XMind 8 快捷鍵大全
虛擬機器棧、本地方法棧和程式計數器是執行緒私有的;
-
程式計數器 (Program Counter Register)
-
是 當前執行緒 執行位元組碼的行號指示器,每一條執行緒都有一個獨立的程式計數器。
-
執行Java方法,計數器記錄的時虛擬機器位元組碼指令的地址
-
執行Native方法,則為 Undefined
-
-
位元組碼直譯器工作時通過改變這個計數器的值來選取下一條需要執行的位元組碼指令。
-
-
Java虛擬機器棧 (Java Virtual Machine Stack)
-
虛擬機器棧描述的是Java方法執行的執行緒記憶體模型,是執行緒私有的:
-
每個方法被執行時會同步建立一個幀棧 (Stack Frame),用於儲存區域性變量表、運算元棧、動態連線、方法介面等資訊。方法的呼叫和退出對應著幀棧在虛擬機器棧中的入棧和出棧。
-
-
區域性變量表:存放了編譯期可知的基本資料型別、物件引用和ReturnAddress型別。
-
儲存空間以區域性變數槽 (slot) 為基本單位。
-
區域性變量表所需要的 slot 的數量是在編譯期間完成分配並確定的。
-
-
虛擬機器棧的兩種異常狀況:
-
StackOverFlowError:執行緒請求的棧深度大於虛擬機器所允許的深度。
-
OutOfMemoryError:如果虛擬機器棧容量可以動態擴充套件,但是棧擴充套件無法獲取足夠的記憶體。
-
-
-
本地方法棧 (Native Method Stacks)
和Java虛擬機器棧類似,不過是為本地方法服務,也會存在StackOverFlowError和OutOfMemoryError;
(部分虛擬機器會將 Java虛擬機器棧 和 本地方法棧 合併)
-
Java堆 (Java heap)
是虛擬機器所管理記憶體中最大的一塊,在虛擬機器啟動時建立,為所有執行緒共享。
-
幾乎所有的物件例項和陣列都應當在堆上分配。
-
由於即使編譯技術的進步和逃逸分析技術的強大,棧上分配和標量替換優化並不在堆上申請記憶體。
-
-
Java堆是垃圾收集器管理的記憶體區域,因此堆也被稱為 Garbage Collected Heap。
-
執行緒共享的Java堆可以劃分出多個執行緒私有的分配緩衝區 (Thread Local Allocation Buffer, TLAB)
-
Java堆邏輯上是連續的,物理上可以儲存在不連續的空間中。
-
Java堆可以選擇擴充套件或不擴充套件,如果堆空間被用盡會丟擲 OutOfMemoryError。
-
-
方法區 (Method area)
方法區也為多個執行緒共共享,是堆的一個邏輯區域,被稱為非堆 (Non-Heap)。
-
方法區中儲存了已經被虛擬機器載入的型別資訊、常量、靜態變數、即時編譯器編譯後的程式碼快取等資料。
-
JDK7之後,字串常量池、靜態變數等被移到了Java堆中。
-
JDK8中,拋棄了永久代的觀念,永久代中的內容被本地記憶體中實現的元空間替代。
-
-
方法區可以選擇大小是否可擴充套件,還可以選擇是否實現垃圾回收機制。(因為方法很少改變,某隻意義上是永久的)
-
方法區無法申請新記憶體時,會丟擲 OutOfMemoryError。
-
-
執行時常量池 (Runtime Constant Pool)
是方法區的一部分,JDK8之後被移到元空間中,而字串常量池則在堆中。
-
Class檔案包含類的版本、欄位、方法、介面等資訊外,還包含常量池表 (Constant Pool Table)
-
其用於存放編譯器生成的各種字面量和符號引用,在類載入後被方法方法區的執行時常量區中。
-
-
執行時常量池是動態的,Class檔案常量池是靜態的,可以在執行期間將新的常量放入執行時常量池中。
-
{String}.intern()
尋找當前字串所屬的常量字串,如果沒有則建立並返回常量字串。
-
-
常量池無法申請記憶體時會丟擲 OutOfMemoryError。
-
-
直接記憶體 (Direct Memory)
-
不是執行時資料區的一部分,但是這部分割槽域被頻繁使用,且會丟擲OutOfMemoryError。
-
HotSpot虛擬機器
-
物件的建立
-
Java虛擬機器遇到一條位元組碼 new 指令時採取的動作:
-
檢查new指令的引數是否能夠在常量池中定義到一個類的符號引用。
-
-
檢測這個類是否被已經被載入、解析和初始化過。
-
否則執行相應類的載入過程。
-
-
虛擬機器為新生物件分配記憶體
物件所需要的記憶體在類載入完成後便可完全確定。
-
Java堆的記憶體是否規整,取決於所採用的垃圾收集器是否帶空間壓縮整理(Compact)功能。
-
Java堆的記憶體是規整的:用指標記錄已用記憶體和空閒記憶體的分界點,分配記憶體將指標移動即可。這種分配方式被稱為指標碰撞 Bump The Pointer。
-
Java堆的記憶體不規則:維護一個Free List,在列表中找到足夠的空間分配給物件例項。
-
-
保證併發下物件建立的執行緒安全的兩種方法:
-
對分配記憶體空間的動作進行同步處理——通過CAS(樂觀鎖)配上失敗重試的方式。
-
為執行緒劃分不同的記憶體分配空間,每個執行緒預留一個本地執行緒分配緩衝 (TLAB),LTAB用完後在分配新的快取區時採用同步鎖定。
-
-
-
將初始化的記憶體空間 (不包括物件頭) 置0,TLAB可能會將此操作提前。
-
設定物件的物件頭 —— 雜湊碼(實際上呼叫hashcode時才計算),類的元資料,分代年齡等。
-
呼叫程式設計師編寫的構造方法。
-
建立物件的過程:
-
HotSpot直譯器程式碼:
// 確保常量池中存放的是已解釋的類
if(!constants->tag_at(index).is_unresolved_klass()) {
// 斷言確保是 klassOop
opp entry = (klassOop)*constants->obj_at_addr(index);
assert(entry->is_klass(), "Should be resolved klass");
// 斷言確保是 instanceKlassOop
KlassOop k_entry = (klassOop) entry;
assert(k_entry->klass_part()->oop_is_instance(),"Should be instanceKlass");
instanceKlass* ik = (instanceKlass*) k_entry->klass_part();
// 確保物件所屬型別已經經過初始化過程
if(ik->is_initialized() && ik->can_be_fastpath_allocated()) {
// 獲取物件長度
size_t obj_size = ik->size_helper();
oop result = NULL;
// TLAB會提前置0,只有指標碰撞的分配方式時才需要置0
bool need_zore = !ZeroTLAB;
// 如果是TLAB方法,則需要為其在TLAB中分配記憶體
if(UseTLAB) {
result = (oop) THREAD->tlab().allocate(obj_size);
}
if (result == NULL) {
need_zero = true;
// 優先在eden中分配物件
// 目前主流的垃圾收集器將heap分為新生代(Eden,Survior1,urvior2)和老生代
// 獲取已用記憶體和空閒記憶體分界點的指標,將其移動
retry:
HeapWord* compare_to = *Universe::heap()->top_addr();
HeapWord* new_top = compare_to + obj_size();
// 如果還有足夠的記憶體空間則進行分配
if(new_top <= *Universe::heap()->end_addr()) {
// 採用CAS的方法,申請失敗則retry
if(Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
goto retry;
}
result = (oop) compare_to;
}
}
if(result != NULL) {
if(need_zero) {
// oppDesc是物件頭
HeapWord* to_zero = (HeapWord*)result+sizeof(oopDesc)/oppSize;
// 物件頭無需置0
obj_size -= sizeof(oppDesc) / oppSize;
if(obj_size > 0) {
memset(to_zero, 0, obj_size * HeapWordSize);
}
}
// 根據是否啟用偏向鎖,設定物件頭資訊
if(UseBiaseLocking) {
result->set_mark(ik->prototype_header());
} else {
result->set_mark(markOopDesc::prototype());
-
-