Object类
Object类是Java中其他所有类的祖先,没有Object类Java面向对象无从谈起。作为其他所有类的基类,Object具有哪些属性和行为,是Java语言设计背后的思维体现。
Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。
Object 类位于 java.lang 包中,编译时会自动导入,我们创建一个类时,如果没有明确继承一个父类,那么它就会自动继承 Object,成为 Object 的子类。
Object类方法
Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入。Object类没有定义属性,一共有13个方法:
类构造器public Object();
大部分情况下,Java中通过形如 new A(args…)形式创建一个属于该类型的对象。其中A即是类名,A(args…)即此类定义中相对应的构造函数。通过此种形式创建的对象都是通过类中的构造函数完成。为体现此特性,Java中规定:在类定义过程中,对于未定义构造函数的类,默认会有一个无参数的构造函数,作为所有类的基类,Object类自然要反映出此特性,在源码中,未给出Object类构造函数定义,但实际上,此构造函数是存在的。
当然,并不是所有的类都是通过此种方式去构建,也自然的,并不是所有的类构造函数都是public。
private static native void registerNatives();
registerNatives函数前面有native关键字修饰,Java中,用native关键字修饰的函数表明该方法的实现并不是在Java中去完成,而是由C/C++去完成,并被编译成了.dll,由Java去调用。方法的具体实现体在dll文件中,对于不同平台,其具体实现应该有所不同。用native修饰,即表示操作系统,需要提供此方法,Java本身需要使用。具体到registerNatives()方法本身,其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。
既然如此,可能有人会问,registerNatives()修饰符为private,且并没有执行,作用何以达到?其实,在Java源码中,此方法的声明后有紧接着一段静态代码块:
private static native void registerNatives();
static {
registerNatives();
}
protected native Object clone() throws CloneNotSupportedException;
clone()方法又是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法,具体的实现是有C/C++完成的。clone英文翻译为”克隆”,其目的是创建并返回此对象的一个副本,而克隆出来的副本与原来的对象没有任何关系的。Java术语表述为:clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。
默认的是浅拷贝
void LibraryCallKit::copy_to_clone(Node* obj, Node* alloc_obj, Node* obj_size, bool is_array, bool card_mark) {
//首先做一些基本判断,例如对象不为空
assert(obj_size != NULL, "");
Node* raw_obj = alloc_obj->in(1);
assert(alloc_obj->is_CheckCastPP() && raw_obj->is_Proj() && raw_obj->in(0)->is_Allocate(), "");
AllocateNode* alloc = NULL;
if (ReduceBulkZeroing) {
// We will be completely responsible for initializing this object -
// mark Initialize node as complete.
alloc = AllocateNode::Ideal_allocation(alloc_obj, &_gvn);
// The object was just allocated - there should be no any stores!
guarantee(alloc != NULL && alloc->maybe_set_complete(&_gvn), "");
// Mark as complete_with_arraycopy so that on AllocateNode
// expansion, we know this AllocateNode is initialized by an array
// copy and a StoreStore barrier exists after the array copy.
alloc->initialization()->set_complete_with_arraycopy();
}
//分配初始指针
Node* src = obj;
Node* dest = alloc_obj;
Node* size = _gvn.transform(obj_size);
// 根据是否为数组决定对象头结构
int base_off = is_array ? arrayOopDesc::length_offset_in_bytes() :
instanceOopDesc::base_offset_in_bytes();
// base_off:
// 8 - 32-bit VM
// 12 - 64-bit VM, compressed klass
// 16 - 64-bit VM, normal klass
if (base_off % BytesPerLong != 0) {
assert(UseCompressedClassPointers, "");
if (is_array) {
// Exclude length to copy by 8 bytes words.
base_off += sizeof(int);
} else {
// Include klass to copy by 8 bytes words.
base_off = instanceOopDesc::klass_offset_in_bytes();
}
assert(base_off % BytesPerLong == 0, "expect 8 bytes alignment");
}
src = basic_plus_adr(src, base_off);
dest = basic_plus_adr(dest, base_off);
// Compute the length also, if needed:
Node* countx = size;
countx = _gvn.transform(new (C) SubXNode(countx, MakeConX(base_off)));
countx = _gvn.transform(new (C) URShiftXNode(countx, intcon(LogBytesPerLong) ));
const TypePtr* raw_adr_type = TypeRawPtr::BOTTOM;
bool disjoint_bases = true;
generate_unchecked_arraycopy(raw_adr_type, T_LONG, disjoint_bases,
src, NULL, dest, NULL, countx,
/*dest_uninitialized*/true);
// If necessary, emit some card marks afterwards. (Non-arrays only.)
if (card_mark) {
assert(!is_array, "");
// Put in store barrier for any and all oops we are sticking
// into this object. (We could avoid this if we could prove
// that the object type contains no oop fields at all.)
Node* no_particular_value = NULL;
Node* no_particular_field = NULL;
int raw_adr_idx = Compile::AliasIdxRaw;
post_barrier(control(),
memory(raw_adr_type),
alloc_obj,
no_particular_field,
raw_adr_idx,
no_particular_value,
T_OBJECT,
false);
}
// Do not let reads from the cloned object float above the arraycopy.
if (alloc != NULL) {
// Do not let stores that initialize this object be reordered with
// a subsequent store that would make this object accessible by
// other threads.
// Record what AllocateNode this StoreStore protects so that
// escape analysis can go from the MemBarStoreStoreNode to the
// AllocateNode and eliminate the MemBarStoreStoreNode if possible
// based on the escape status of the AllocateNode.
insert_mem_bar(Op_MemBarStoreStore, alloc->proj_out(AllocateNode::RawAddress));
} else {
insert_mem_bar(Op_MemBarCPUOrder);
}
}
public final native Class<?> getClass();
getClass()也是一个native方法,返回的是此Object对象的类对象/运行时类对象Class<?>。效果与Object.class相同。
首先解释下”类对象”的概念:在Java中,类是是对具有一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例。作为概念层次的类,其本身也具有某些共同的特性,如都具有类名称、由类加载器去加载,都具有包,具有父类,属性和方法等。于是,Java中有专门定义了一个类,Class,去描述其他类所具有的这些特性,因此,从此角度去看,类本身也都是属于Class类的对象。为与经常意义上的对象相区分,在此称之为”类对象”。
public boolean equals(Object obj);
Object类中关于equals()方法的定义:
public boolean equals(Object obj) {
return (this == obj);
}
由此可见,Object原生的equals()方法内部调用的正是==,与==具有相同的含义。既然如此,为什么还要定义此equals()方法?
equlas()方法的正确理解应该是:判断两个对象是否相等。那么判断对象相等的标尺又是什么?
如上,在object类中,此标尺即为==。当然,这个标尺不是固定的,其他类中可以按照实际的需要对此标尺含义进行重定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。如此可以增加类的功能型和实际编码的灵活性。当然了,如果自定义的类没有重写equals()方法来重新定义此标尺,那么默认的将是其父类的equals(),直到object基类。
对于equals方法的实现,则需要满足:
- 自反性:对于任意非null的x,x.equals(x)为true
- 对称性:对于任意非null的x和y,如果x.equals(y),那么y.equals(x)
- 传递性:对于任意非null的x,y和z,如果x.equals(y)并且y.equals(z),那么x.equals(z)
- 一致性:对于任意非null的x和y,无论执行多少次x.equals(y)结果都是一样的
- 对于任意非null的x,x.equals(null)返回false
public native int hashCode();
hashCode()方法返回一个整形数值,表示该对象的哈希码值。
hashCode()具有如下约定:
1).在Java应用程序程序执行期间,对于同一对象多次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未做修改。在Java应用程序的一次执行到另外一次执行,同一对象的hashCode()返回的哈希码无须保持一致; 但是,在程序重启后同一个对象的hashCode不用和之前那次运行的hashCode保持一致。但是考虑如果在分布式的情况下,如果对象作为key,最好还是保证无论在哪台机器上运行多少次,重启多少次,不同机器上,同一个对象(指的是两个equals对象),的hashCode值都一样(原因之后会说的)。 例如这里的Object对于hashCode的实现,在当前次运行,这个对象的存储地址是不变的。所以hashCode不变,但是程序重启后就不一定了。对于String的hashCode实现:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
2).如果两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等;
3).反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不一定相等。
即严格的数学逻辑表示为: 两个对象相等 <=> equals()相等 => hashCode()相等。因此,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时可以推理出:hasCode()不相等 => equals()不相等 <=> 两个对象不相等。
可能有人在此产生疑问:既然比较两个对象是否相等的唯一条件(也是冲要条件)是equals,那么为什么还要弄出一个hashCode(),并且进行如此约定,弄得这么麻烦?
其实,这主要体现在hashCode()方法的作用上,其主要用于增强哈希表的性能。
以集合类中,以Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现)
在此需要纠正一个理解上的误区:对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode()相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode()返回的哈希码可能相同。
public String toString();
toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
默认的实现就是class名字@hashcode(如果hashCode()方法也是默认的话,那么就是如之前所述地址)
toString()方法我们会经常用到,即使没有显式调用,但当我们使用System.out.println(obj)时,其内部也是通过toString()来实现的
toString()是由对象的类型和其哈希码唯一确定,同一类型但不相等的两个对象分别调用toString()方法返回的结果可能相同。
wait(…) / notify() / notifyAll()
wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。这几个方法主要用于java多线程之间的协作.
public final native void wait(long timeout) throws InterruptedException;
public final native void notify();
public final native void notifyAll();
wait():调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法。
notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。
wait(long timeout)/wait(long timeout, int nanos):调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notisfy()/notisfyAll()方法,或超过指定的超时时间量。
notify()/notifyAll():唤醒在此对象监视器上等待的单个线程/所有线程。
wait(…) / notify() | notifyAll()一般情况下都是配套使用。 |
那么底层实现是怎么回事呢? 首先我们需要先明确JDK底层实现共享内存锁的基本机制。 每个Object都有一个ObjectMonitor,这个ObjectMonitor中包含三个特殊的数据结构,分别是CXQ(实际上是Contention List),EntryList还有WaitSet;一个线程在同一时间只会出现在他们三个中的一个中。首先来看下CXQ: 一个尝试获取Object锁的线程,如果首次尝试(就是尝试CAS更新轻量锁)失败,那么会进入CXQ;进入的方法就是CAS更新CXQ指针指向自己,如果成功,自己的next指向剩余队列;CXQ是一个LIFO队列,设计成LIFO主要是为了:
- 进入CXQ队列后,每个线程先进入一段时间的spin自旋状态,尝试获取锁,获取失败的话则进入park状态。这个自旋的意义在于,假设锁的hold时间非常短,如果直接进入park状态的话,程序在用户态和系统态之间的切换会影响锁性能。这个spin可以减少切换;
- 进入spin状态如果成功获取到锁的话,需要出队列,出队列需要更新自己的头指针,如果位于队列前列,那么需要操作的时间会减少 但是,如果全部依靠这个机制,那么理所当然的,CAS更新队列头的操作会非常频繁。所以,引入了EntryList来减少争用:
假设Thread A是当前锁的Owner,接下来他要释放锁了,那么如果EntryList为null并且cxq不为null,就会从cxq末尾取出一个线程,放入EntryList(注意,EntryList为双向队列),并且标记EntryList其中一个线程为Successor(一般是头节点,这个EntryList的大小可能大于一,一般在notify时,后面会说到),这个Successor接下来会进入spin状态尝试获取锁(注意,在第一次自旋过去后,之后线程一直处于park状态)。如果获取成功,则成为owner,否则,回到EntryList中。 这种利用两个队列减少争用的算法,可以参考: Michael Scott’s “2Q” algorithm 接下来,进入我们的正题,wait方法。如果一个线程成为owner后,执行了wait方法,则会进入WaitSet:
Object.wait()底层实现
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
//检查线程合法性
Thread *const Self = THREAD;
assert(Self->is_Java_thread(), "Must be Java thread!");
JavaThread *jt = (JavaThread *) THREAD;
DeferredInitialize();
//检查当前线程是否拥有锁
CHECK_OWNER();
EventJavaMonitorWait event;
// 检查中断位
if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
if (JvmtiExport::should_post_monitor_waited()) {
JvmtiExport::post_monitor_waited(jt, this, false);
}
if (event.should_commit()) {
post_monitor_wait_event(&event, 0, millis, false);
}
TEVENT(Wait - ThrowIEX);
THROW(vmSymbols::java_lang_InterruptedException());
return;
}
TEVENT(Wait);
assert(Self->_Stalled == 0, "invariant");
Self->_Stalled = intptr_t(this);
jt->set_current_waiting_monitor(this);
//建立放入WaitSet中的这个线程的封装对象
ObjectWaiter node(Self);
node.TState = ObjectWaiter::TS_WAIT;
Self->_ParkEvent->reset();
OrderAccess::fence();
//用自旋方式获取操作waitset的lock,因为一般只有owner线程会操作这个waitset(无论是wait还是notify),所以竞争概率很小(除非响应interrupt事件才会有争用),采用spin方式效率高
Thread::SpinAcquire(&_WaitSetLock, "WaitSet - add");
//添加到waitset
AddWaiter(&node);
//释放锁,代表现在线程已经进入了waitset,接下来要park了
Thread::SpinRelease(&_WaitSetLock);
if ((SyncFlags & 4) == 0) {
_Responsible = NULL;
}
intptr_t save = _recursions; // record the old recursion count
_waiters++; // increment the number of waiters
_recursions = 0; // set the recursion level to be 1
exit(true, Self); // exit the monitor
guarantee(_owner != Self, "invariant");
// 确保没有unpark事件冲突影响本次park,方法就是主动post一次unpark
if (node._notified != 0 && _succ == Self) {
node._event->unpark();
}
// 接下来就是park操作了
。。。。。。。。。
。。。。。。。。。
}
当另一个owner线程调用notify时,根据Knob_MoveNotifyee这个值,决定将从waitset里面取出的一个线程放到哪里(cxq或者EntrySet)
Object.notify()底层实现
void ObjectMonitor::notify(TRAPS) {
//检查当前线程是否拥有锁
CHECK_OWNER();
if (_WaitSet == NULL) {
TEVENT(Empty - Notify);
return;
}
DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
//决定取出来的线程放在哪里
int Policy = Knob_MoveNotifyee;
//同样的,用自旋方式获取操作waitset的lock
Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify");
ObjectWaiter *iterator = DequeueWaiter();
if (iterator != NULL) {
TEVENT(Notify1 - Transfer);
guarantee(iterator->TState == ObjectWaiter::TS_WAIT, "invariant");
guarantee(iterator->_notified == 0, "invariant");
if (Policy != 4) {
iterator->TState = ObjectWaiter::TS_ENTER;
}
iterator->_notified = 1;
Thread *Self = THREAD;
iterator->_notifier_tid = Self->osthread()->thread_id();
ObjectWaiter *List = _EntryList;
if (List != NULL) {
assert(List->_prev == NULL, "invariant");
assert(List->TState == ObjectWaiter::TS_ENTER, "invariant");
assert(List != iterator, "invariant");
}
if (Policy == 0) { // prepend to EntryList
if (List == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
List->_prev = iterator;
iterator->_next = List;
iterator->_prev = NULL;
_EntryList = iterator;
}
} else if (Policy == 1) { // append to EntryList
if (List == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
// CONSIDER: finding the tail currently requires a linear-time walk of
// the EntryList. We can make tail access constant-time by converting to
// a CDLL instead of using our current DLL.
ObjectWaiter *Tail;
for (Tail = List; Tail->_next != NULL; Tail = Tail->_next);
assert(Tail != NULL && Tail->_next == NULL, "invariant");
Tail->_next = iterator;
iterator->_prev = Tail;
iterator->_next = NULL;
}
} else if (Policy == 2) { // prepend to cxq
// prepend to cxq
if (List == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
iterator->TState = ObjectWaiter::TS_CXQ;
for (;;) {
ObjectWaiter *Front = _cxq;
iterator->_next = Front;
if (Atomic::cmpxchg_ptr(iterator, &_cxq, Front) == Front) {
break;
}
}
}
} else if (Policy == 3) { // append to cxq
iterator->TState = ObjectWaiter::TS_CXQ;
for (;;) {
ObjectWaiter *Tail;
Tail = _cxq;
if (Tail == NULL) {
iterator->_next = NULL;
if (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) {
break;
}
} else {
while (Tail->_next != NULL) Tail = Tail->_next;
Tail->_next = iterator;
iterator->_prev = Tail;
iterator->_next = NULL;
break;
}
}
} else {
ParkEvent *ev = iterator->_event;
iterator->TState = ObjectWaiter::TS_RUN;
OrderAccess::fence();
ev->unpark();
}
if (Policy < 4) {
iterator->wait_reenter_begin(this);
}
// _WaitSetLock protects the wait queue, not the EntryList. We could
// move the add-to-EntryList operation, above, outside the critical section
// protected by _WaitSetLock. In practice that's not useful. With the
// exception of wait() timeouts and interrupts the monitor owner
// is the only thread that grabs _WaitSetLock. There's almost no contention
// on _WaitSetLock so it's not profitable to reduce the length of the
// critical section.
}
//释放waitset的lock
Thread::SpinRelease(&_WaitSetLock);
if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {
ObjectMonitor::_sync_Notifications->inc();
}
}
为什么这三个方法都定义在 Object 类而不是 Thread 类中??
只有同一把锁上的调用 wait() 被阻塞的线程可以被 notify() 唤醒,对于不同锁的线程不起作用,而 Java 中任意对象都可以作为一把锁,所以这三个方法可以被任意对象调用,应定义在 Object 类中,线程通过锁对象找到对应的 Monitor
如果定义在 Thread 类中,线程在调用 wait() 和 notify() 的时候针对的是哪一把锁便无从判断,提高了管理上的难度
protected void finalize();
finalize方法主要与Java垃圾回收机制有关。首先我们看一下finalized方法在Object中的具体定义:
protected void finalize() throws Throwable { }
我们发现Object类中finalize方法被定义成一个空方法,为什么要如此定义呢?finalize方法的调用时机是怎么样的呢?
首先,Object中定义finalize方法表明Java中每一个对象都将具有finalize这种行为,其具体调用时机在:JVM准备对此对形象所占用的内存空间进行垃圾回收前,将被调用。由此可以看出,此方法并不是由我们主动去调用的(虽然可以主动去调用,此时与其他自定义方法无异)。
当确定一个对象不会被其他方法再使用时,该对象就没有存在的意义了,就只能等待JVM的垃圾回收线程来回收了。垃圾回收是以占用一定内存资源为代价的。System.gc();就是启动垃圾回收线程的语句。当用户认为需要回收时,可以使用Runtime.getRuntime( ).gc( );或者System.gc();来回收内存。(System.gc();调用的就是Runtime类的gc( )方法) 当一个对象在回收前想要执行一些操作,就要覆写Object类中的finalize( )方法。
注意到抛出的是Throwable,说明除了常规的异常Exceprion外,还有可能是JVM错误。说明调用该方法不一定只会在程序中产生异常,还有可能产生JVM错误。