JDK核心JAVA原始碼解析(1)
想寫這個系列很久了,對自己也是個總結與提高。原來在學JAVA時,那些JAVA入門書籍會告訴你一些規律還有法則,但是用的時候我們一般很難想起來,因為我們用的少並且不知道為什麼。知其所以然方能印象深刻並學以致用。
首先我們從所有類的父類Object開始:
1. Object類
(1)hashCode方法和equals方法
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 () {
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;
}
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其他資料結構時會有意想不到的結果,後面會在回顧這個問題。
(2)toString( )方法——列印物件的資訊
這個沒啥好說的,預設的實現就是class名字@hashcode(如果hashCode()方法也是預設的話,那麼就是如之前所述地址)
public String toString() {
return getClass().getName()+"@"+Integer.toHexString(hashCode());
}
(3) wait(), notify(), notifyAll()
這些屬於基本的Java多執行緒同步類的API,都是native實現:
public final native void wait(long timeout) throws InterruptedException;
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()底層實現
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();
}
}
對於NotifyAll就很好推測了,這裡不再贅述;
(4)clone方法
預設的是淺拷貝,至於為什麼,這裡不贅述,我們直接看原始碼:
對於object的clone,我們需要考慮是否為陣列,還有jvm環境等等,具體可以參考我的另一篇文章:垃圾收集分析(1)-Java物件結構(上)
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);
}
}
(5) finalize()方法——物件的回收
當確定一個物件不會被其他方法再使用時,該物件就沒有存在的意義了,就只能等待JVM的垃圾回收執行緒來回收了。垃圾回收是以佔用一定記憶體資源為代價的。System.gc();就是啟動垃圾回收執行緒的語句。當用戶認為需要回收時,可以使用Runtime.getRuntime( ).gc( );或者System.gc();來回收記憶體。(System.gc();呼叫的就是Runtime類的gc( )方法)
當一個物件在回收前想要執行一些操作,就要覆寫Object類中的finalize( )方法。
protected void finalize() throws Throwable { }
注意到丟擲的是Throwable,說明除了常規的異常Exceprion外,還有可能是JVM錯誤。說明呼叫該方法不一定只會在程式中產生異常,還有可能產生JVM錯誤。