- Java虛擬機運行時數據區(qū)
- 方法區(qū):存儲 類信息,、常量、靜態(tài)變量,、即使編譯器編譯后的代碼等數據,,也有別名叫做非堆。 方法區(qū)其中有包含有 運行時常量池,,用于存放編譯期生成的各種字面量和符號引用,。其中,可通過String.intern()方法將字符串放入運行時常量池中,。
- 堆:存儲的是類實例對象,,數組。 JVM 所管理的內存中最大的一塊,。Java堆是被所有線程共享的一塊內存區(qū)域,,在虛擬機啟動時創(chuàng)建。 從內存回收的角度來看,,由于現在收集器基本都采用 分代收集算法,,所以堆可以細分為 新生代 和老年代;再細分 新生代可以分為:Eden空間,,From Survivor空間和To Survivor空間等,。
- 虛擬機棧:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀用于存儲局部變量表,、操作數棧、動態(tài)鏈接,、方法出口等信息,。每個方法從調用直至執(zhí)行完成的過程,就對應著一個棧幀在虛擬機棧中入棧和出棧的過程,。
- 本地方法棧: 本地方法棧服務于虛擬機執(zhí)行Native方法服務。作用與虛擬機棧相似,。
- 程序計數器:程序計數器是一塊較小的內存空間,,它可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器,字節(jié)碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,,分支,、循環(huán)、跳轉,、異常處理,、線程恢復等基礎功能都需要依賴這個計數器來完成。每條線程都需要有一個獨立的程序計數器
- Java類加載機制
- 裝載
- 加載方式:
- 從本地系統(tǒng)中直接加載
- 通過網絡下載class文件
- 從歸檔文件中加載class文件
- 從專有數據庫中提取class文件
- 將Java源文件 動態(tài)編譯為class文件,,也就是運行時計算而成
- 從加密文件中獲取
- 連接
- 驗證
-
驗證java版本號,,文件格式,
元數據校驗(是否有父類,,是否繼承了final類等java 語法)
字節(jié)碼驗證(運行檢查,,棧數據類型和操作碼操作參數是否吻合)
- 準備
- private static final int a =1; constantValue 通知虛擬機生成常量賦值,,不需要開辟內存,。 基于final static 修飾的 基本數據類型和String起作用
- 解析
- 初始化
-
初始化什么時候被觸發(fā)? 類 主動使用到的時候
1 創(chuàng)建類的實例,,也就是new
2 訪問某個類或者接口的靜態(tài)變量,,給該靜態(tài)變量賦值
3 調用類的靜態(tài)方法
4.反射 (class.forname("..."))
5.初始化某個類的子類,則其父類也會被初始化
6.java 虛擬機啟動時被標明為啟動類的類(如springboot啟動類)
- 使用
- 卸載
-
1.該類所有的實例都已經被回收,,即java堆中不存在該類的任何實例
2. 加載該類的classloader 已經被回收
3. 該類對應的java.lang.Class對象沒有任何地方被引用,,無法在任何地方通過反射訪問該類的方法
- 類加載器
- Java對象內存布局
- Java對象內存分為三部分: 對象頭,,實例數據,,對齊填充
-
- 對象頭
- Mark Word : 哈希碼,分代年齡,,線程持有的鎖,,偏向鎖ID,偏向時間戳,,鎖狀態(tài),,還有1bit的占位符
- class Pointer: 指的是類型指針,對象指向它的類元數據的指針,,虛擬機通過這個指針來確定這個對象是哪個類的實例
-
實例數據
- 對象真正存儲的有效信息,,代碼中所定義的各種類型的字段內容。無論是從父類繼承下來的,,還是在子類中定義的,,都需要記錄起來。這部分的存儲順序會受到虛擬機分配策略參數和字段在Java遠嗎中定義順序的影響,。HotSpot虛擬機默認的分配策略為longs/doubles,、ints、shorts/chars,、bytes/booleans,、oops,從分配策略中可以看出,,先攻寬度的字段總是被分配到一起的,。在滿足這個前提條件的情況下,在父類中定義的變量會出現在子類之前,。
- 對齊填充
- HotSpot VM 要求對象大小必須是8字節(jié)的整數倍
- 對象定位方式:
- Java對象的生命周期
- 創(chuàng)建階段
- 應用階段
- 不可見階段
- 不可達階段
- 收集階段
- 終結階段
- 空間重分配階段
- 對象創(chuàng)建過程:
- 判斷對象是否已經“死”了的算法有兩種:
- 引用計數算法: 給對象中添加一個引用計數器,,每當有一個地方引用它時,,計數器值就加1;當引用失效時,,計數器值就減1,;任何時刻計數器為0的情況,都是不可能再被使用的,。
- 引用計數算法的缺陷就是它很難解決對象之間相互循環(huán)引用的問題
- 可達性分析算法:通過一系列的成為“GC Roots"的對象作為起始點,,從這些節(jié)點開始向下搜索,,搜索所走過的路徑成為引用鏈,當一個對象到GC roots沒有任何引用鏈相連時,,則證明此對象是不可用的,。
- 在Java中,可作為GC roots的對象包括下面幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中JNI(即一般說的Native方法)引用的對象,。
-
引用分類:
- 垃圾收集算法
- 標記-清除算法
- 不足:
- 效率問題:標記和清除兩個過程的效率不高
- 空間問題:標記清除后會產生大量不連續(xù)的內存碎片,,碎片太多導致以后再程序運行過程中需要分配較大對象時,無法找到足夠的連續(xù)內存而不得不提前觸發(fā)另一次垃圾收集動作,。
- 復制算法:將可用的內存按容量劃分大小相等的涼快,,每次只使用其中的一塊。當這一塊的內存用完了,,就將還存活著的對象復制到另一塊上面,,然后再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區(qū)進行內存回收,,內存分配時也就不用考慮內存碎片等復雜情況,,只要移動堆頂指針,按順序分配內存即可,,簡單高效,。
- 不足:
- 當對象存活率較高時,要進行較多的復制操作,,效率會降低,。
- 標記-整理算法:標記過程仍然與”標記-清除算法一樣,后續(xù)讓所有存活的對象都向一端移動,,然后直接清理掉端邊界以外的內存,。
- 分代收集算法:
- 這種算法并沒有什么新的思想,只是根據對象存活周期的不同將內存劃分為幾塊,。一般是把Java堆分為新生代和老年代,,這樣就可以根據各個年代的特點采用最適合的收集算法。在新生代中,,每次垃圾收集時都發(fā)現有大批對象死去,,只有少量存活,那就選用復制算法,,只需要付出少量存活對象的復制成本就可以完成,。而老年代中因為對象存活率高,、沒有額外空間對他進行分配擔保,就必須使用“標記-清理”或者“標記-整理”算法來進行回收,。
|