程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 《深入理解JAVA虛擬機》——學習筆記

《深入理解JAVA虛擬機》——學習筆記

編輯:關於JAVA

《深入理解JAVA虛擬機》——學習筆記。本站提示廣大學習愛好者:(《深入理解JAVA虛擬機》——學習筆記)文章只能為提供參考,不一定能成為您想要的結果。以下是《深入理解JAVA虛擬機》——學習筆記正文


JVM內存模型以及分區

JVM內存分為:

1.方法區:線程共享的區域,存儲已經被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據

2.堆:線程共享的區域,存儲對象實例,以及給數組分配的內存區域也在這裡。

3.虛擬機棧:線程隔離的區域,每個線程都有自己的虛擬機棧,生命周期和線程相同。虛擬機棧描述方法執行的內存模型,以站棧幀為單位,每個棧幀存儲和方法運行有關的局部變量表、操作數棧、動態鏈接、方法返回地址等信息。

4.程序計數器:線程隔離的區域,每個線程都有自己的程序計數器,存儲程序當前執行的字節碼的行號。

5.本地方法棧:線程隔離,和虛擬機棧類似,是虛擬機調用Native方法時使用的。

 

堆的分區,以及各個分區的特點:

Java堆是垃圾收集器管理的主要區域,按照分代收集算法的劃分,堆內存空間可以繼續細分為年輕代,老年代。年輕代又可以劃分為較大的Eden區,兩個同等大小的From Survivor,To Survivor區。默認的Eden區和Survivor區的大小比例為8:1:1,這個比例可以調節。在為新創建的對象分配內存的時候先將對象分配到Eden區和From Survivor區,在立即回收時,會將Eden區和Survivor區還存活的對象復制到To Survivor區中,如果To Survivor區的大小不能容納存活的對象,會把存活的對象分配到老年區。總體來說,新創建的小對象會放在年輕代,年輕代的對象大多在下一次垃圾回收時被回收,老年代存儲大的對象和存活時間長的對象。

 

對象的創建方法,對象的內存布局,對象的訪問定位

對象的創建:

1.普通對象的創建過程:虛擬機遇到一條new指令時,首先檢查這個指令的參數(類的類型)是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類時候已經被加載、解析、初始化過,如果沒有要執行類加載過程。

2.數組對象的創建:虛擬機遇到一條newarray字節碼指令會在內存中直接分配一塊區域。

3.Class對象的創建:在虛擬機加載類的時候,通過類的全限定名獲取此類的二進制字節流,再通過文件驗證後把字節流代表的靜態結構轉化為方法區的運行時數據結構,並且在內存中生成一個代表這個類的Class對象,存在方法區中,作為這個類的各種數據的訪問入口。

 

對象的內存布局:

對象在內存中的布局分為三塊區域:對象頭、實例數據、對齊填充

對象頭:存儲對象自身的運行時數據,包括哈希嗎,GC分代年齡,鎖狀態標識,線程持有的鎖,偏向線程ID,偏向時間戳等;對象頭的另外一部分是類型指針,即對象指向它在方法區中的類元數據的指針,虛擬機通過這個指針來確定該對象是哪個類的實例。如果對象是一個數組,對象頭還有一塊用語記錄數組長度的數據。

實例數據:對象真正存儲的有效信息,是在類中定義的各種類型的字段內容。

對齊填充:虛擬機要求對象的大小必須是8字節的整數倍,對齊填充起占位符的作用,保證對象大小為8字節的整數倍。

 

對象的訪問定位:Java程序通過棧上的引用數據操作堆中的具體對象,對象訪問方式有兩種:句柄訪問,直接指針訪問。

句柄訪問:Java堆劃分出一塊區域用作句柄池,引用中存儲對象的句柄地址,句柄中才實際包含著對象實例數據和對象類型數據各自的具體地址信息。

直接指針訪問:棧中的引用直接指向對象在堆中的地址,對象在頭數據中指向方法區中其類元數據的地址。

使用句柄的好處是引用中存儲的是穩定的句柄地址,在對象被移動(垃圾回收導致對象的移動)時只會改變局並重的實例數據指針。使用直接指針訪問的好處是速度更快。

 

垃圾回收的判定方法:引用計數法,引用鏈法

引用計數法:給對象添加一個引用計數器,有對象引用計數器加1,引用失效計數器減1,計數器為0表示對象不再被使用,可以被回收。

引用鏈法(可達性分析):通過GC Roots作為起點,當一個對象到到GC Roots沒有任何引用鏈相連時,證明對象時不可用的。

可作為GC Roots的對象是虛擬機棧中引用的對象、本地方法棧中引用的對象、方法區中類靜態屬性引用的對象,方法區中常量引用的對象(執行上下文和全局性引用)

 

Java的四種引用類型及特點:

1.強引用:程序中普遍存在的,類似“String s=”hello wold””這類的引用,強引用的對象不會被回收。

2.軟引用:有用但是非必須的對象在系統將要發生內存溢出之前會對軟引用的對象進行垃圾回收,SoftReference類實現軟引用。

3.弱引用:非必須的對象,被弱引用關聯的對象只能存活到下一次垃圾收集發生之前。

4.虛引用:最弱的引用關系,不能通過虛引用取得對象的實例,為對象設置虛引用的唯一目的就是在這個對象被收集器回收時收到一個系統通知。

四種引用強度依次減弱,強軟弱虛。

 

GC的三種收集算法的原理和特點,用途,優化思路

三種垃圾收集算法:復制算法,標記-清除算法、標記-整理算法

標記-清除算法:首先標記出所有需要回收的對象,標記完成後統一回收所有被標記的對象。缺點:標記和清除兩個過程效率都不高;標記清楚後會產生空間碎片,空間碎片導致分配較大對象時可能提前出發垃圾回收。

復制算法:將可用內存分為兩個區域,每次只使用其中一塊,當使用的那一塊內存用完時,將還存活的對象復制到另外一塊內存中,然後把已使用過的內存空間一次清理掉。優點:解決的空間碎片問題,實現簡單。缺點:將內存縮小為兩塊,內存使用率不高。復制操作頻繁效率變低。

標記-整理算法:可回收對象標記後,讓所有存活的對象向一端移動,然後清理掉邊界以外的內存。優點:不會產生空間碎片,比復制算法提高了內存空間利用率。

復制算法用在年輕代的垃圾回收中,標記整理和標記清除算法用在老年代垃圾回收的收集器中。

 

GC收集器有哪些?CMS和G1收集器的特點

GC收集器按照回收區域不同,新生代有Serial,Parnew,Paralell Scanvage,老年代有Serial Old,CMS,Parallel old,還有新生代老年代通用的G1;

Serial 和Serial old是早期jdk中發布的垃圾收集器,特點是都為單線程,新生代采用復制算法,老年代采用標記整理算法,兩個垃圾收集器在工作的時候必須要停掉所有的用戶線程,直到收集完成後才能回復用戶線程,由於是單線程工作方式,沒有線程交互的開銷所以能夠活的最高的單線程收集效率,使用在client模式下的虛擬機。

ParNew收集器是Serial收集器的多線程版本,是年輕代的垃圾收集器,可以和Serial old以及CMS老年代收集器搭配使用。Parnew在單CPU環境中的性能沒有Serial好,因為單CPU環境下的多線程按照時間順序串行執行,還要承擔線程間交互的額外開銷,不過在多cpu環境下,Parnew的性能就會好很多,是運行在server模式下的虛擬機首選的新生代收集器。

在jdk1.4時新推出的垃圾收集器是Parallel Scanvage 和對應的Parallel Old,新生代基於復制算法,老年代基於標記整理算法.Parallel Scanvage也是並行性的多線程收集器,它和Parnew 的區別在於兩者的關注點不同。Parnew關注於減少垃圾回收時用戶線程停頓的時間,而Parllel Scanvage 關注點事獲得最大的吞吐量,也就是CPU運行用戶代碼與CPU總消耗時間的比值。停頓時間短適合於和用戶有交互的程序,吞吐量高則可以高效的利用CPU時間,盡快完成運算任務,主要是和在後台運算不需要太多的交互任務。

Jdk1.5時推出了能夠和用戶線程並發執行的CMS收集器,CMS是老年代垃圾收集器。CMS是一種以獲取最短回收停頓時間為目標的收集器,基於標記清除算法來實現。它的工作過程先後分為初始標記、並發標記、重新標記、並發清除四個步驟,其中初始標記和重新標記是需要停頓用戶線程的,並發標記和並發清理過程是可以和用戶線程並發執行的,在整體垃圾收集時間裡,初始標記和重新標記所占的時間很少,重新標記階段又是可以多個垃圾回收線程並行執行的,所以整體用戶線程停頓的時間很短。CMS的缺點:對CPU資源敏感,CMS默認啟動的垃圾回收線程數為(CPU數量+3)/4,在並發階段由於占用用戶線程導致應用變慢,cpu不足4個時候對用戶程序影響很大;CMS無法處理在並發清理階段新產生的垃圾,只有等下一次垃圾回收標記後才能清除;CMS基於標記清除算法會產生空間碎片,CMS的解決方式是在進行Full GC時開啟內存整理,這一過程無法並發,延長了用戶線程的停頓時間。

G1收集器是在jdk1.7時推出的用語新生代和來年代的垃圾收集器,面向server模式。G1把內存區域劃分成多個大小相同的獨立區域,G1跟蹤每個區域裡面垃圾堆積的價值大小,在後台維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的區域,這種收集策略可以在有限時間內獲取盡可能高的收集效率。G1垃圾回收過程:初始標記(單線程,停頓)、並發標記(單線程,並發)、最終標記(多線程,並行,停頓)、篩選回收(多線程,並行,停頓)。

 

Minor GC和Full GC分別發生在什麼時候?

當創建對象分配的內存空間不足時會啟動一次Minor GC,收集新生代的Eden區和From Survivor區,把還存活的對象分配到To Survivor區,如果To Survivor區的空間不足以容納存活的對象,會把存活的對象分配到老年代,如果老年代也沒有足夠的空間會啟動一次Full GC。

 

類加載過程:加載、驗證、准備、解析、初始化

虛擬機的類加載機制就是把描述類的數據從Class文件(或者其他途徑)加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型。

加載:1.通過一個類的全限定名獲取定義此類的二進制字節流2、將這個字節流所戴曉的靜態結構轉化為方法區的運行時數據結構3、在內存中生成一個代表這個類的Class對象,作為方法區這個類的各種數據的訪問入口。

驗證:1、文件格式驗證,保證輸入的字節流在格式上符合Class文件的格式規范,保證輸入的字節流能正確的解析,只有通過這個驗證,字節流才會存儲在方法區之內2、元數據驗證,對類的元數據進行語義校驗,保證類描述的信息符合Java語言規范。比如驗證類的是否實現了父類或者接口中的方法等3、字節碼驗證,通過數據流和控制流的分析,確保類的方法符合邏輯,不會在運行時對虛擬機產生危害4、符號引用校驗,發生在解析階段,確保解析階段將符號引用轉化為直接飲用的正常執行。

准備:正式為類變量(static)分配內存,並設置類變量初始值(數據類型的零值),這些變量所使用的內存在方法區中分配。

解析:虛擬機將常量池內的符號引用轉化為直接飲用,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。

初始化:初始化階段才真正執行類中定義的Java代碼,初始化階段是執行類構造器<init>方法的過程。<init>方法(類構造器)是由編譯器自動收集類中的靜態變量和靜態代碼塊合並產生的。子類和父類的初始化過程優先級為:父類類構造器->子類類構造器->父類對象構造函數->子類對象構造函數。類中靜態類變量和靜態代碼塊是按照在類中定義的順序執行的。

 

什麼時候進行類的初始化?

JVM規定了有且僅有5中情況——對類進行主動引用,必須立即執行類的初始化。

1)、遇到new,putstatic,getstatic,invokespecial四條字節碼指令的時候,如果沒有進行類的初始化要立即初始化。這四條字節碼指令對應的編程中的環境為:使用new關鍵字實例化對象,讀取或設置類的靜態變量,調用類的靜態方法。

2)、使用java.lang.reflect包對類進行反射的時候,如果沒有初始化要立即初始化。

3)、初始化一個類的時候,如果其父類沒有進行初始化要先出發父類的初始化

4)、虛擬機啟動的時候,main方法所在的主類會被虛擬機先初始化

5)、使用動態語言在lava.lang.invoke.MethodHandle實例最後的解析結果是REF_getdtatic,REF_putStatic,REF_invokeStatic的方法句柄,這個句柄對應的類沒有被初始化需要先觸發其初始化。

 

雙親委派模型:

類加載器的雙親委派模型是指從頂層到底層分別是啟動類加載器、擴展類加載器、應用程序類加載器、自定義類加載器。類加載器之間的父子關系不是通過繼承來實現,而是通過組合來實現。雙親委派模型的工作過程是:如果一個類加載器收到了類加載的請求,首先把這個請求委派給父類加載器去完成,所有的加載請求最終都應該傳送到頂層的啟動類加載器中,只有當父類反饋自己無法完成類加載請求的時候,自加載器才會嘗試自己去加載。

使用雙親委派模型的好處:java類隨著他的加載器一起具備了帶有優先級的層次結構,最基礎的類由頂層的類加載器加載,這樣保證在程序中使用該類的地方使用的都是這同一個類。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved