想写这个系列很久了,对自己也是个总结与提高。原来在学 JAVA 时,那些 JAVA 入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么。知其所以然方能印象深刻并学以致用。 首先我们从所有类的父类 Object 开始:
- public native int hashCode();public boolean equals(Object obj) {return(this== obj);
- }
Java 内规定,hashCode 方法的结果需要与 equals 方法一致。也就是说,如果两个对象的 hashCode 相同,那么两个对象调用 equals 方法的结果需要一致。那么也就是在以后的 java 程序设计中,你需要同时覆盖这两个方法来保证一致性。 在 Object 代码中,hashCode 是 native 的,非 java 代码实现。主要原因是它的实现方法是通过将对象在内存中所处于的位置转换成数字,这个数字就是 hashCode。但是这个内存地址实际上 java 程序并不关心也是不可知的。这个地址是由 JVM 维护并保存的,所以实现是 native 的。 如果两个 Object 的 hashCode 一样,那么就代表两个 Object 的内存地址一样,实际上他们就是同一个对象。所以,Object 的 equals 实现就是看两个对象指针是否相等(是否是同一个对象) 在 JAVA 程序设计中,对于 hashCode 方法需要满足:
1. 在程序运行过程中,同一个对象的 hashCode 无论执行多少次都要保持一致。但是,在程序重启后同一个对象的 hashCode 不用和之前那次运行的 hashCode 保持一致。但是考虑如果在分布式的情况下,如果对象作为 key,最好还是保证无论在哪台机器上运行多少次,重启多少次,不同机器上,同一个对象(指的是两个 equals 对象),的 hashCode 值都一样(原因之后会说的)。 例如这里的 Object 对于 hashCode 的实现,在当前次运行,这个对象的存储地址是不变的。所以 hashCode 不变,但是程序重启后就不一定了。对于 String 的 hashCode 实现:
- public int hashCode() {inth = hash;if(h ==0&&value.length >0) {charval[] =value;for(inti =0; i <value.length; i++) {
- h =31* h + val[i];
- }
- hash = h;
- }returnh;
- }
String 就是一种典型的适合在分布式的情况下作为 key 的存储对象。无论程序何时在哪里运行,同一个 String 的 hashCode 结果都是一样的。 2. 如果两个对象是 euqal 的,那么 hashCode 要相同 3. 建议但不强制对于不相等的对象的 hashCode 一定要不同。这样可以有效减少 hash 冲突 hashCode 主要用于集合类中的 hashmap(hashset 的底层实现也是 hashmap)。之后我们在 Set 源代码部分会继续细说。
对于 equals 方法的实现,则需要满足: 1. 自反性:对于任意非 null 的 x,x.equals(x) 为 true 2. 对称性:对于任意非 null 的 x 和 y,如果 x.equals(y),那么 y.equals(x) 3. 传递性:对于任意非 null 的 x,y 和 z,如果 x.equals(y) 并且 y.equals(z),那么 x.equals(z) 4. 一致性:对于任意非 null 的 x 和 y,无论执行多少次 x.equals(y) 结果都是一样的 5. 对于任意非 null 的 x,x.equals(null) 返回 false
自己实现这些方法一定要满足这些条件,否则再用 Java 其他数据结构时会有意想不到的结果,后面会在回顾这个问题。
这个没啥好说的,默认的实现就是 class 名字 @hashcode(如果 hashCode() 方法也是默认的话,那么就是如之前所述地址)
- publicStringtoString() {returngetClass().getName()+"@"+Integer.toHexString(hashCode());
- }
这些属于基本的 Java 多线程同步类的 API,都是 native 实现:
- public final native void wait(longtimeout)throwsInterruptedException;public final native void notify();public final native void notifyAll();
那么底层实现是怎么回事呢? 首先我们需要先明确 JDK 底层实现共享内存锁的基本机制。 每个 Object 都有一个 ObjectMonitor,这个 ObjectMonitor 中包含三个特殊的数据结构,分别是 CXQ(实际上是 Contention List),EntryList 还有 WaitSet;一个线程在同一时间只会出现在他们三个中的一个中。首先来看下 CXQ: 一个尝试获取 Object 锁的线程,如果首次尝试(就是尝试 CAS 更新轻量锁)失败,那么会进入 CXQ;进入的方法就是 CAS 更新 CXQ 指针指向自己,如果成功,自己的 next 指向剩余队列;CXQ 是一个 LIFO 队列,设计成 LIFO 主要是为了: 1. 进入 CXQ 队列后,每个线程先进入一段时间的 spin 自旋状态,尝试获取锁,获取失败的话则进入 park 状态。这个自旋的意义在于,假设锁的 hold 时间非常短,如果直接进入 park 状态的话,程序在用户态和系统态之间的切换会影响锁性能。这个 spin 可以减少切换; 2. 进入 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() 底层实现
- voidObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {//检查线程合法性
- Thread *constSelf = 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");//添加到waitsetAddWaiter(&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 1exit(true,Self);// exit the monitorguarantee(_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() 底层实现
- voidObjectMonitor::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 cxqiterator->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();
- }
- }
对于 NotifyAll 就很好推测了,这里不再赘述;
默认的是浅拷贝,至于为什么,这里不赘述,我们直接看源码: 对于 object 的 clone,我们需要考虑是否为数组,还有 jvm 环境等等,具体可以参考我的另一篇文章:垃圾收集分析(1)-Java 对象结构 (上)
- voidLibraryCallKit::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);
- }
- }
当确定一个对象不会被其他方法再使用时,该对象就没有存在的意义了,就只能等待 JVM 的垃圾回收线程来回收了。垃圾回收是以占用一定内存资源为代价的。System.gc(); 就是启动垃圾回收线程的语句。当用户认为需要回收时,可以使用 Runtime.getRuntime().gc(); 或者 System.gc(); 来回收内存。(System.gc(); 调用的就是 Runtime 类的 gc() 方法) 当一个对象在回收前想要执行一些操作,就要覆写 Object 类中的 finalize( ) 方法。
- protected void finalize()throwsThrowable { }
注意到抛出的是 Throwable,说明除了常规的异常 Exceprion 外,还有可能是 JVM 错误。说明调用该方法不一定只会在程序中产生异常,还有可能产生 JVM 错误。
来源: http://blog.csdn.net/zhxdick/article/details/56673610