當提到JVM類加載的時候,我們是在談什麼?

當我們提到JVM的時候,前提是我們知道啥是JVM,談這事的基礎,至少知道它是java 虛擬機。此時至少要知道什麼是虛擬機,如果聽說過VM ware的話,需要知道這個VM是Virtual Machine的簡稱,這樣就知道了JVM 是全稱是Java Virtual Machine。
那虛擬機是幹啥的呢,用Java編寫的程序,計算機是沒法識別出來的,它根本就不懂這門語言,那麼怎麼辦?就要有角色給它翻譯翻譯,這個角色就是JVM,但是我們編寫的.java文件JVM也沒法直接識別,所以,需要把java程序編程成.class文件,JVM才能識別這個程序並且運行它。JVM的好處體現在,無論我們使用的是什麼操作系統,只要有JVM環境存在,就可以運行java程序。

1.生命週期

討論JVM生命週期的目的是,我們需要知道它是怎麼來的,也需要知道它是怎麼沒的。
簡單的是:JVM隨着程序的運行啓動,程序的結束而停止。
這裏分爲兩個線程,守護線程和普通線程。
守護線程體現在GC(垃圾回收),這個後面再說。守護線程是JVM自己的。普通的線程是運行的程序的。

2.JVM內存模型

這個比較基礎的是,大家都知道有:程序計數器、方法區、堆、虛擬機棧、本地方法棧、局部變量表、運行時常量池。
其中具體在我這篇文章裏有體現:JVM運行時數據區域

3.JVM類加載

瞭解前面兩點,就可以開始談論jvm類加載過程了。
剛剛也說了,代碼編譯後,會生成二進制字節流文件(*.class)。
JVM把Class文件中的類描述數據加載到內存,並對數據進行校驗、準備、解析、初始化,使這些數據最終成爲可以被JVM直接使用的Java類型,這就叫做JVM的類加載機制。是不是並不複雜?
不過裏面有一些名詞沒有解釋清楚。讓我們一起康康!!(震聲)
類加載過程

ok,我們逐步來說。

a.首先是加載

這是類加載過程的第一個階段,JVM在這個階段完成了三件事:
1.通過類的全限定名,獲取class文件。
2.把class文件代表的靜態存儲結構轉化爲運行時數據結構,放在方法區。
3.在內存生成Class對象,代表這個類,作爲方法區裏這個類的訪問入口,這個Class對象雖然是對象,但是存在方法區中,而不是堆。

b.連接

連接分爲三個小步驟:檢驗、準備、編譯。
1.驗證:被加載的類結構是否正確,保障安全性。
2.準備:給類的靜態變量分配內存(在方法區),並賦值,這個是默認初始值。
3.解析:類的常量池內的符號引用替換爲直接引用。符號引用就是一組符號來描述目標,可以是任何字面量;直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。

c.初始化

初始化是給靜態變量設置正確的初始值。這裏和前面準備過程進行區分,因爲準備過程只是賦默認初始值,這裏纔是設置正確的值。
值得注意的是以下情況,必須對類進行初始化:
1.new 字節碼創建類的實例,或者調用靜態方法的時候,或者get static、put static讀取或設置靜態字段的時候。
2.反射調用的時候。
3.初始化一個類,如果其父類米有初始化,先觸發父類初始化。
4.虛擬機啓動時,指定的主類,比如包含main方法的類,虛擬機會初始化這個類。
5.使用jdk1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有進行初始化,則需要先觸發其初始化。(這個之前我還真不知道~)
以上這幾種情況,被稱作“主動引用”。

###4.GC 垃圾回收
這裏簡單說一下前面提到的垃圾回收機制。當然和JVM類加載關係倒是沒特別特別大。
有一篇我記錄的文章也談論過這個,詳細的可以看這個:JVM垃圾收集器與內存分配策略

先說哪些區域需要垃圾回收??爲啥要回收內存??
垃圾回收主要集中在堆和方法區,這兩個區域內存分配和回收是動態的。回收內存的意義在於空間是有限的,就算內存特別大,也會有上限。
大家應該也知道垃圾回收的兩種算法,一個是引用計數,一個是可達性分析。引用計數算法由於存在兩個對象相互引用的可能性,並沒有在java中使用。
可達性分析算法,基本思想是通過GC Root的對象作爲起點,然後開始向下搜索。走過的路徑稱爲引用鏈,當一個對象到GC Root沒有引用鏈的話,此對象可以被回收了。

GC Root對象的選取條件包括:
1.棧(局部變量表)中引用的對象
2.方法區類靜態屬性引用的對象
3.方法區常量引用對象
4.本地方法棧中JNI引用的對象

引用狀態也分爲四種:強引用、軟引用、弱引用、虛引用
強引用不參與垃圾回收,軟引用在內存溢出前回收,弱引用垃圾回收的時候直接收走,虛引用也收走,回收時收到一個系統通知。
要知道的是,不可達的對象被標記一次之後還能再搶救一下,如果被標記兩次就直接GG了。具體是finalize方法,如果對象覆蓋這個方法,被標記一下之後會被放入小黑屋——F-Queue,如果對象在finalize方法裏面成功自救,關聯上GC Root,那他就不會被馬上回收,否則直接被幹掉。

方法區裏面廢棄常量和無用的類會被回收,至於什麼是無用的類。
1.java堆裏面不存在該類的實例
2.加載該類的ClassLoader已經被回收
3.對象沒有被引用。
符合以上三點,就是無用的類。

剛剛提到了標記!這就要簡單說一下垃圾收集算法。

  • 標記-清除算法
  • 複製算法
  • 標記-整理算法
  • 分代收集算法

1.標記-清除算法
最基礎最簡單,先掃一圈,標記對象,然後再刪除,這種方法效率比較低下。而且清除之後產生了不連續的內存碎片。以後分配大對象的時候,會以爲空間不足,就會提前觸發垃圾回收動作。
2.複製算法
效率比較高,將可用內存分成兩塊,每次只用一塊,用完之後會把活着的對象扔到另外一塊區域。然後直接清理之前的區域。這個算法效率提升了,但是直接把內存縮了一半。
3.標記-整理算法
可以說是標記-清除2.0,區別在於並沒有直接幹掉對象,而是先標記,然後讓存活的對象向一端進行移動。然後再清理。
4.分代收集算法
現在用的就是分代收集,把複製算法和標記-整理進行結合使用,我們知道根據對象的生命週期,可以分爲新生代和老年代。
新生代的對象一般是大批量死去,少量存活,可以類比新陳代謝快,所有對於新生代使用複製算法,而且1:1很不科學,現在是新生代內存分一塊Eden和兩塊Survivor。
老年代的對象,存活率比較高,而且大多不易回收,新陳代謝慢,所以採用標記清理算法。

然後執行這些算法的就是收集器了,java這麼多年發展,有很多收集器。全說比較費勁,簡述一下
1.Serial
最老的,採用複製算法,單線程會影響其他線程工作。
2.ParNew
Serial的多線程版本。
3.Parallel Scavenge
由於使用的依然是複製算法,它也依然是一個新生代收集器。Parallel Scavenge主要引入吞吐量這個概念,
吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)。它提供了參數可以控制垃圾售後機停頓使勁和吞吐量。
4.Serial Old
Serial的老年代版本。標記-整理算法
5.Parallel Old
Parallel Scavenge 老年代版本。
6.CMS(Conrrurent Mark Sweep)
目標是獲取最短回收停頓時間,使用標記-清除。這個可能會提前觸發Full GC
7.G1
這個比較NB,現在用的也是它。最主要的是支持了分代收集。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章