今天咱們來討論下 Java 虛擬機,經過一系列常見的問題來逐漸深刻了解 JVM 建立對象過程,內存佈局,類加載以及 GC 回收算法等機制。java
十問 JVM 問題整理:算法
Java虛擬機建立對象的過程 (使用 new 的方式)數組
對象的內存佈局安全
雙親委派機制多線程
JVM的內存佈局app
JVM 中的類加載機制函數
垃圾回收算法與垃圾收集器佈局
在什麼狀況下對象從年輕代轉移到老年代學習
Java方法區中存哪些東西, 如何控制方法區的大小編碼
JVM 如何判斷對象已死,可達性算法分析
happen-before 原則
下面咱們從 Java 建立對象的過程開始,逐漸深刻了解下 JVM。
Java虛擬機建立對象的過程 (使用 new 的方式):
虛擬機接到一條new指令時,首先檢查這個類的參數是否能在常量池中定位到一個類的符號引用而且檢查這個符號引用表明的類是否已被加載、解析和初始化過。
類加載檢查經過後,虛擬機爲新生對象分配內存(堆中分配內存方法:碰撞指針與空閒列表)
內存分配完成後,虛擬機須要將分配到的內存空間初始化爲零值(默認值)
對對象進行必要的設置,如設置對象的哈希碼,分代年齡等信息
執行 init 方法,按照程序值初始化。
通過以上步驟虛擬機會新建立一個對象,對象的內存佈局以下:
對象頭包括兩部分信息,第一部分用於存儲對象自身運行數據,如哈希碼,GC分代年齡,鎖狀態標誌,線程持有鎖,偏向線程ID等,官方成爲 Mark Word。
第二部分是類型指針,即對象指向它的類元數據信息
如圖所示:
瞭解完了建立對象的過程與對象內存佈局,在開始討論 JVM 內存佈局以前首先要了解下 JVM 的雙親委派機制:
主要目的是爲了肯定類在 JVM 中的惟一性,確保程序運行穩定,安全。
當須要加載類時,類自己的加載器不會去加載而是先調用父類加載器,將請求委派給父類加載器,每層都是如此,所以全部加載最終都會到達頂層的啓動類加載器。只有當父類加載器反饋不能加載,纔會將加載任務給子加載器。
例如經過雙親委派機制使得用戶自定義的 String 類不會被加載,從而保證系統的安全穩定。
啓動類加載器 BootStrap 是 C++實現,主要負責加載 JAVA_HOME lib下類或被 –Xbootclasspath 參數指定的類庫不能直接使用該加載器;
擴展類加載器 Extension 主要負責加載 JAVA_HOME下 lib/ext 下的類或系統變量 java.ext.dirs 指定類路徑下的類庫開發者能夠直接使用;
應用類加載器 Application 加載用戶指定的路徑即 classpath 下的類若是應用程序沒有指定加載器則默認使用該加載器。
JVM的內存佈局
如圖所示:
程序計數器:內存中較小的一塊區域。當前線程在執行字節碼的行號指示器。
線程是私有的,每一個線程都有一個程序計數器,線程之間相互獨立,是虛擬機中惟一一個沒有規定OutOfMemoryError 狀況的區域。
虛擬機棧
棧也叫棧內存,主管Java程序的運行,是在線程建立時建立,它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對於棧來講不存在垃圾回收問題。
8種基本類型的變量+對象的引用變量+實例方法都是在函數的棧內存中分配。
棧幀中主要保存3 類數據:
本地變量(Local Variables):輸入參數和輸出參數以及方法內的變量;
棧操做(Operand Stack):記錄出棧、入棧的操做;
棧幀數據(Frame Data):包括類文件、方法等等。
本地方法棧
同虛擬機棧相同,只不過本地方法棧是爲native 方法服務
堆
Java 堆是被線程共享的一塊區域。 Java 堆是用來存放實例對象和數組對象。因爲如今有了逃逸分析技術,也能夠將對象分配在棧上。
同時 Java堆也能夠是物理上不連續的區域,只要邏輯上連續便可。
在堆中爲對象分配空間的方法有指針碰撞 和 空閒列表。
指針碰撞:內存規整
空閒列表:內存不連續
選擇哪種分配方法取決因而否規整,是否規整取決於垃圾回收算法是否壓縮。
方法區
與堆同樣,是線程共享區域。用於存儲已經被虛擬機加載的類的類信息、常量池、靜態變量、編譯後的代碼,運行時常量池(存儲編譯器生產的各類字面值與符號引用),方法區不足時拋出OutOfMemoryError PermGen Space 異常。
堆外內存
即直接內存。堆外內存能減小IO時的內存複製,實現零拷貝,不須要GC。
JVM 中的類加載機制
主要過程分爲 :加載驗證 準備 解析 初始化
加載:將類轉換成二進制字節流,將字節流表明的靜態結構轉化爲動態結構,在內存中生產一個表明這個類的 java.lang.Class 對象,做爲方法區中的這個類的訪問入口。
驗證:驗證Class文件中的字節流是否符合java 虛擬機規範,包括文件格式,元數據驗證等。
準備:爲類變量分配內存並設置類變量的初始值,分配的內存在方法區中(類變量是類模板對象)
解析:將常量池中的符號引用轉化爲直接引用。
符號引用是使用一組符號描述鎖引用的目標。Class 文件中的常量池中包括字面值與符號引用(字段的名稱和描述符、方法名稱和描述符、類和接口的權限定名),在 Class文件阿忠不會保存各個方法字段的最終佈局信息。所以這些符號不住安慰是沒法獲得真正的內存入口地址。
直接引用於虛擬機實現的內存佈局有關,能夠直接指向目標的指針,偏移量或者指向目標的句柄。
初始化階段:該階段纔會真正的開始執行類中定義java代碼。初始化時執行類構造器 clinit()方法的過程。
垃圾回收算法與垃圾收集器
垃圾回收算法 :
標記清除算法:將全部須要回收的對象進行標記,標記結束後對標記的對象進行回收,可是效率低,會形成大量碎片。
複製算法:複製算法將空間分爲兩部分,每次使用其中的一部分,當一塊內存用完了,就將這塊的全部對象複製到另外一塊,將已使用的塊清除。不會產生碎片,會浪費必定的內存空間。在堆中的年輕代使用該算法。
由於年輕代對象多爲生命週期比較短的對象。
年輕代將內存分爲一個 Eden ,兩個 survivor(from區、to區)。每次使用Eden與一個survivor。當回收時,將from 與Eden 中存活的對象複製到另外一個to區,最後清理掉Eden與from。當survivor 與Eden 中存活對象大小超過另外一個 survivor,則須要老年代來擔保。
標記整理算法
複製算法在對象存活率較高時,複製會使效率下降,根據老年代特色,使用標記整理算法。標記以後將全部存活對象移向一端,將其餘的清理,解決了碎片的問題。
分代收集算法
年輕代,老年代根據各自不一樣特色採用不一樣算法。
垃圾收集器:
Serial 收集器是單線程的收集器,在進行垃圾回收時須要中止其餘的全部工做線程。
ParNew 收集器是Serial 的多線程版本。在單線程的環境下,parNew毫不比 Serial 收集器有更好的優點,隨着spu增長能夠提現出優點。
Parallel Scavenge 收集器,年輕代收集器,多線程並行收集,使用複製算法,與parNew 類似。
Serial Old 是serial 在老年代的版本
CMS:是一種獲取最短停頓時間爲目標的收集器。基於標記清除的算法實現。
G1收集器 能夠用於年輕代與老年代的垃圾收集器。採用標記整理算法。
7. 將對象從年輕代到老年代是如何判斷該對象執行力多久,在什麼狀況下轉移? 哪些對象在老年代中?
輕GC 發生在年輕代,頻率高速度快。
Full GC 是清理整個堆空間—包括年輕代和永久代。
通常新生成的對象都出如今 Eden 區,當 Eden 區被填滿時,全部通過垃圾回收還存活的對象將被複制到兩個 Survivor 區域中的一個,假定是 From 區,當From 區也被填滿時,這個區域通過垃圾回收仍存活的對象將會被複制進 To 區,原From 區被清空,而且從 Eden 區過來的數據將直接進入To區。當To區也被填滿時,以前從From區過來的數據若是仍在活動,則將被放入年老代,須要注意的是,兩個survivor 區域總有一個是空的。
經過年齡計數器。對象每通過一個GC 存活,年齡計數器加一。當年齡超過設定的值,則將其經過擔保機制轉移到老年代。
老年代對象:
大對象(字符串與數組),超過了設定值的對象,直接在老年代中分配。
長期存活的對象進入老年代。
8. Java方法區中存哪些東西? JVM 如何控制方法區的大小以及內存溢出的緣由和解決方法。
方法區大小不是固定的,能夠經過 JVM 參數動態調整。方法區中主要存放常量、靜態變量、虛擬機加載的類信息和編譯後的代碼,運行時常量池。
減小程序中class 的數量。儘可能使用較少的靜態變量。修改–XX : MaxPermSize調大。
錯誤類型爲方法區溢出:PermGen space 錯誤。
9. JVM 如何判斷對象已死,可達性算法分析
首先有一系列的 GC root 根節點。從這些根節點開始向下搜索,走過的路徑成爲引用鏈。當一個對象到 GC root 不存在任何引用鏈時,則此對象不可活,當對象不可活時,可用 finalize() 方法自救。但第二次被回收時則會死亡。
可做爲 GC root 根節點的對象包括:
方法區中常量引用的對象
方法區中靜態屬性引用的對象
虛擬機棧中引用的對象
本地方法中引用的對象
引用類型:
強引用
特色:咱們日常典型編碼Object obj = new Object()中的obj就是強引用。經過關鍵字new建立的對象所關聯的引用就是強引用。 當JVM內存空間不足,JVM寧願拋出OutOfMemoryError運行時錯誤(OOM),使程序異常終止,也不會靠隨意回收具備強引用的「存活」對象來解決內存不足的問題。對於一個普通的對象,若是沒有其餘的引用關係,只要超過了引用的做用域或者顯式地將相應(強)引用賦值爲 null,就是能夠被垃圾收集的了,具體回收時機仍是要看垃圾收集策略。
軟引用
特色:軟引用經過SoftReference類實現。軟引用的生命週期比強引用短一些。只有當 JVM 認爲內存不足時,纔會去試圖回收軟引用指向的對象:即JVM 會確保在拋出 OutOfMemoryError 以前,清理軟引用指向的對象。
弱引用
弱引用經過WeakReference類實現。弱引用的生命週期比軟引用短。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。
虛引用
特色:虛引用也叫幻象引用,經過PhantomReference類來實現。沒法經過虛引用訪問對象的任何屬性或函數。幻象引用僅僅是提供了一種確保對象被 finalize 之後,作某些事情的機制。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收。
10. happen before 原則
若是兩個操做之間具備 happen –before 關係,那麼前一個操做結果會對後一個操做可見。
常見的happen-before原則:
程序順序規則
線程中的每一個操做,happen-before於線程中任意後續操做可見。
鎖規則
對一個鎖解鎖,happen-before於隨後給這個鎖加鎖。
volatile 規則
對 volatile的寫happen-before 於任意後續volatile的讀。
至此,十問JVM的問答也結束了,經過這一系列的問題咱們討論了 JVM 的一些特性,在之後的學習中,咱們還會更加深刻的去探討JVM更底層的一些性質。
參考資料:
《深刻理解 Java虛擬機 第二版》
關注一下,我寫的就更來勁兒啦