jvm筆記

類的加載機制

類的加載

類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。

類的生命週期

類的生命週期包括這幾個部分,加載、連接、初始化、使用和卸載,其中前三部是類的加載的過程,如下圖;
這裏寫圖片描述

  1. 加載,查找並加載類的二進制數據,在Java堆中也創建一個java.lang.Class類的對象
  2. 連接,連接又包含三塊內容:驗證、準備、初始化。1)驗證,文件格式、元數據、字節碼、符號引用驗證;2)準備,爲類的靜態變量分配內存,並將其初始化爲默認值;3)解析,把類中的符號引用轉換爲直接引用
  3. 初始化,爲類的靜態變量賦予正確的初始值
  4. 使用,new出對象程序中使用
  5. 卸載,執行垃圾回收

類初始化時機

  1. 創建類的實例,也就是new的方式
  2. 訪問某個類或接口的靜態變量,或者對該靜態變量賦值
  3. 調用類的靜態方法
  4. 反射(如Class.forName(「com.shengsiyuan.Test」))
  5. 初始化某個類的子類,則其父類也會被初始化
  6. Java虛擬機啓動時被標明爲啓動類的類(Java Test),直接使用java.exe命令來運行某個主類

結束生命週期

•在如下幾種情況下,Java虛擬機將結束生命週期
– 執行了System.exit()方法
– 程序正常執行結束
– 程序在執行過程中遇到了異常或錯誤而異常終止
– 由於操作系統出現錯誤而導致Java虛擬機進程終止

類加載器

這裏寫圖片描述
1. 啓動類加載器:Bootstrap ClassLoader,負責加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,並且能被虛擬機識別的類庫
2. 擴展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher E x t C l a s s L o a d e r D K \jre \lib \ext j a v a . e x t . d i r s j a v a x . 使 3. A p p l i c a t i o n C l a s s L o a d e r s u n . m i s c . L a u n c h e r AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器

JVM類加載機制

  1. 全盤負責,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入
  2. 父類委託,先讓父類加載器試圖加載該類,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類
  3. 緩存機制,緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是爲什麼修改了Class後,必須重啓JVM,程序的修改纔會生效

雙親委派機制:

雙親委派模型的工作流程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委託給父加載器去完成,依次向上,因此,所有的類加載請求最終都應該被傳遞到頂層的啓動類加載器中,只有當父加載器在它的搜索範圍中沒有找到所需的類時,即無法完成該加載,子加載器纔會嘗試自己去加載該類。
1. 當AppClassLoader加載一個class時,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。
2. 當ExtClassLoader加載一個class時,它首先也不會自己去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。
3. 如果BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib裏未查找到該class),會使用ExtClassLoader來嘗試加載;
4. 若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,如果AppClassLoader也加載失敗,則會報出異常ClassNotFoundException。

雙親委派模型意義:

-系統類防止內存中出現多份同樣的字節碼
-保證Java程序安全穩定運行

深入理解Java類加載器

jvm內存結構

這裏寫圖片描述
方法區和堆是所有線程共享的內存區域;而java棧、本地方法棧和程序員計數器是運行是線程私有的內存區域。
1. Java堆(Heap),是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存。
2. 方法區(Method Area),方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
3. 程序計數器(Program Counter Register),程序計數器(Program Counter Register)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。
4. JVM棧(JVM Stacks),與程序計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
5. 本地方法棧(Native Method Stacks),本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務

對象分配規則

  1. 對象優先分配在Eden區,如果Eden區沒有足夠的空間時,虛擬機執行一次Minor GC。
  2. 大對象直接進入老年代(大對象是指需要大量連續內存空間的對象)。這樣做的目的是避免在Eden區和兩個Survivor區之間發生大量的內存拷貝(新生代採用複製算法收集內存)。
  3. 長期存活的對象進入老年代。虛擬機爲每個對象定義了一個年齡計數器,如果對象經過了1次Minor GC那麼對象會進入Survivor區,之後每經過一次Minor GC那麼對象的年齡加1,知道達到閥值對象進入老年區。
  4. 動態判斷對象的年齡。如果Survivor區中相同年齡的所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象可以直接進入老年代。
  5. 空間分配擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大於老年區的剩餘值大小則進行一次Full GC,如果小於檢查HandlePromotionFailure設置,如果true則只進行Monitor GC,如果false則進行Full GC。

控制參數

-Xms設置堆的最小空間大小。
-Xmx設置堆的最大空間大小。
-Xmn:新生代的大小
-XX:NewSize設置新生代最小空間大小。
-XX:MaxNewSize設置新生代最大空間大小。
-XX:PermSize設置永久代最小空間大小。
-XX:MaxPermSize設置永久代最大空間大小。
-Xss設置每個線程的堆棧大小。
-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
-XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。
-XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少爲分配空間所導致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少爲釋放空間所導致的垃圾收集

Minor GC、Major GC和Full GC之間的區別

Minor GC 會清理年輕代的內存。
Major GC 是清理老年代。
Full GC 是清理整個堆空間—包括年輕代和老年代。

過程

  1. 新生代有一個Eden區和兩個survivor區,首先將對象放入Eden區,如果空間不足就向其中的一個survivor區上放,如果仍然放不下就會引發一次發生在新生代的minor GC,將存活的對象放入另一個survivor區中,然後清空Eden和之前的那個survivor區的內存。在某次GC過程中,如果發現仍然又放不下的對象,就將這些對象放入老年代內存裏去。
  2. 大對象以及長期存活的對象直接進入老年區。
  3. 當每次執行minor GC的時候應該對要晉升到老年代的對象進行分析,如果這些馬上要到老年區的老年對象的大小超過了老年區的剩餘大小,那麼執行一次Full GC以儘可能地獲得老年區的空間。

PermGen與Metaspace

常量池

  1. 在java7的時候將字符串常量池則移到java heap
    所有的被intern的String被存儲在PermGen區.PermGen區使用-XX:MaxPermSize=N來設置最大大小,但是由於應用程序string.intern通常是不可預測和不可控的,因此不好設置這個大小。設置不好的話,常常會引起java.lang.OutOfMemoryError: PermGen space
  2. java 8的字符串常量池在堆中實現
    字符串常量池被限制在整個應用的堆內存中,在運行時調用String.intern()增加字符串常量不會使永久代OOM了。

方法區的變化

java8的時候去除PermGen,將其中的方法區移到non-heap中的Metaspace
Metaspace與PermGen之間最大的區別在於:Metaspace並不在虛擬機中,而是使用本地內存。

OOM異常

如果類元數據的空間佔用達到MaxMetaspaceSize設置的值,將會觸發對象和類加載器的垃圾回收。
java.lang.OutOfMemoryError: Metaspace space

好處

 1、字符串存在永久代中,容易出現性能問題和內存溢出。
 2、類及方法的信息等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。
 3、永久代會爲 GC 帶來不必要的複雜度,並且回收效率偏低。
新生代:複製清理;
老年代:標記-清除和標記-整理算法;
聊聊jvm的PermGen與Metaspace

GC算法 垃圾回收

對象存活判斷

  1. 引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數爲0時可以回收。此方法簡單,無法解決對象相互循環引用的問題。
  2. 可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,不可達對象。

算法

  1. 標記 -清除算法
    「標記-清除」(Mark-Sweep)算法,如它的名字一樣,算法分爲「標記」和「清除」兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收掉所有被標記的對象。之所以說它是最基礎的收集算法,是因爲後續的收集算法都是基於這種思路並對其缺點進行改進而得到的。
    它的主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。

  2. 複製算法
    「複製」(Copying)的收集算法,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。
    這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種算法的代價是將內存縮小爲原來的一半,持續複製長生存期的對象則導致效率降低。

  3. 標記-壓縮算法
    複製收集算法在對象存活率較高時就要執行較多的複製操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
  4. 標記-整理
    根據老年代的特點,有人提出了另外一種「標記-整理」(Mark-Compact)算法,標記過程仍然與「標記-清除」算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存
  5. 分代收集算法
    GC分代的基本假設:絕大部分對象的生命週期都非常短暫,存活時間短。
    「分代收集」(Generational Collection)算法,把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記-清理」或「標記-整理」算法來進行回收。

垃圾回收器

  1. Serial收集器,串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。少量的cpu,小的存儲,年輕代的回收。複製算法、串行回收、」stop-the-world」機制
  2. ParNew收集器,ParNew收集器其實就是Serial收集器的多線程版本。年輕代的多線程版,在低延遲的情況下,可以和CMS一起使用。複製算法、並行回收、」stop-the-world」機制
  3. Parallel收集器,Parallel Scavenge收集器類似ParNew收集器,Parallel收集器更關注系統的吞吐量。-XX:+UseParallelGC:年輕代並行,老年代串行;-XX:+UseParallelOldGC:年輕代和老年代都是並行。 複製算法、並行回收、」stop-the-world」機制
  4. Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法,」stop-the-world」
  5. CMS收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的老年代收集器。多線程和「標記-清除」算法,」stop-the-world」
  6. G1收集器,G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特徵

CMS收集器

4個主要階段

  1. 初始標記:標記出內存中那些被根對象集合所連接的目標對象是否可達,會stop-the-world
  2. 併發標記:講之前標記不可以達的對象標記成垃圾對象
  3. 再次標記:確定這些垃圾對象能夠成功且準確的標記,會stop-the-world
  4. 併發清除;釋放無用對象佔用的內存空間

    優點缺點:

  5. 初始標記和再次標記會stop-the-world,時間會很短
    1. 會產生內存碎片

GC分析 命令調優

調優命令

  1. jps,JVM Process Status Tool,顯示指定系統內所有的HotSpot虛擬機進程。
  2. jstat,JVM statistics Monitoring是用於監視虛擬機運行時狀態信息的命令,它可以顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。
  3. jmap,JVM Memory Map命令用於生成heap dump文件
  4. jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果後,可以在瀏覽器中查看
  5. jstack,用於生成java虛擬機當前時刻的線程快照。
  6. jinfo,JVM Configuration info 這個命令作用是實時查看和調整虛擬機運行參數。

堆外內存

finalize