玩轉Java虛擬機(八)

打卡學習JVM,第八天

本人學習過程中所整理的代碼,源碼地址

- JVM內存結構
在這裏插入圖片描述

  • Java虛擬機棧:描述的是Java方法的執行模型——每個方法執行的時候都會創建一個棧幀用於存放局部變量表,操作棧,動態鏈接,方法出口等信息。一個方法的執行過程,就是這個方法對於棧幀的入棧與出棧過程,線程隔離
  • 程序計數器
  • 本地方法棧:主要用於處理本地方法
  • 堆:線程共享,與堆相關的一個重要概念是垃圾收集器。現代幾乎所有的垃圾收集器都是採用的分代收集算法,所以對空間也基於這一點進行了相應的劃分:新生代和老年代。Eden空間,From Survivor空間與To Survivor空間

堆裏存放的是對象的實例,是Java虛擬機管理內存中最大的一塊,GC主要的工作區域,爲了高效的GC,會把堆細分更多的子區域

  • 方法區:存放了每個Class的結構信息,包括常量池、字段描述、方法描述,GC的非主要工作區域(主要回收廢棄常量與無用類)。永久代,從JDK1.8開始,已經徹底廢棄了永久代,使用元空間
  • 運行時常量池:方法區的一部分內容
  • 直接內存:與Java NIO密切相關,JVM通過堆上的DirectByteBuffer來操作直接內存

JVM運行時數據區域示例

public void method(){
	Object obj = new Object();
}
  • 生成了兩部分的內存區域:1)obj這個引用變量,因爲是方法內的變量(局部變量),放到Java虛擬機棧中 2)真正的Object class的實例對象放到堆裏面
  • 上述的new語句一共消耗12個bytes,JVM規定引用佔用4個bytes(在JVM Stack),而空對象是8個bytes(在Heap)
  • 方法結束後,對於的棧中的變量馬上回收,但是堆中的對象要等到GC來回收

- Java對象的創建過程

new關鍵字創建對象的三個步驟:

1. 在堆內存中創建出對象的實例

  • 指針碰撞:前提是堆中的空間通過一個指針進行分隔,一側是已被佔用的空間,另一側是未被佔用的空間
  • 空閒鏈表:前提是堆內存空間中已被使用與未被使用的空間是交織在一起的,這是虛擬機就需要通過一個列表來記錄哪些空間是空閒的,哪些空間是已被佔用的,接下來找出可以容納下新創建對象的且未被使用的空間,在此空間存放該對象,同時還要修改列表上的記錄

2. 爲對象的實例成員變量賦初值

  • 對象在內存中的佈局:對象頭,實例數據(即我們在一個類中所聲明的各項信息),對其填充(可選)
  • 引用訪問對象的方式:使用句柄的方式,使用直接指針的方法
    3. 將對象的引用返回

- jcmd(從JDK1.7開始增加的命令)

  • jcmd pid VM.flage:查看JVM的啓動參數
  • jcmd pid help:列出當前運行的Java進程可以執行的操作
  • jcmd pid help JFR.dump:查看具體命令的選項
  • jcm pid PerfCoubter.print:查看JVM性能相關的參數
  • jcmd pid VM.uptime:查看JVM的啓動時常
  • jcmd pid GC.class_histogram:查看系統中類的統計信息
  • jcmd pid Thread.print:查看線程堆棧信息
  • jcmd pid GC.heap_dump filename:到處Heap_dump文件,到處的文件可以通過jvisualvm查看
  • jcmd pid VM.system_peoperties:查看JVM的屬性信息

以下部分轉載自一篇很不錯的文章——Java 永久代去哪兒了,原文鏈接:https://www.infoq.cn/article/Java-PERMGEN-Removed/,文章版權歸原作者所有,僅供學習交流使用!如有侵權,請私信博主,立即刪除!

- Java 永久代去哪兒了
在 Java 虛擬機(以下簡稱 JVM)中,類包含其對應的元數據,比如類的層級信息,方法數據和方法信息(如字節碼,棧和變量大小),運行時常量池,已確定的符號引用和虛方法表。

在過去(當自定義類加載器使用不普遍的時候),類幾乎是“靜態的”並且很少被卸載和回收,因此類也可以被看成“永久的”。另外由於類作爲 JVM 實現的一部分,它們不由程序來創建,因爲它們也被認爲是“非堆”的內存。

在 JDK8 之前的 HotSpot 虛擬機中,類的這些“永久的”數據存放在一個叫做永久代的區域。永久代一段連續的內存空間,我們在 JVM 啓動之前可以通過設置 -XX:MaxPermSize 的值來控制永久代的大小,32 位機器默認的永久代的大小爲 64M,64 位的機器則爲 85M。永久代的垃圾回收和老年代的垃圾回收是綁定的,一旦其中一個區域被佔滿,這兩個區都要進行垃圾回收。但是有一個明顯的問題,由於我們可以通過‑XX:MaxPermSize 設置永久代的大小,一旦類的元數據超過了設定的大小,程序就會耗盡內存,並出現內存溢出錯誤 (OOM)。

**備註:**在 JDK7 之前的 HotSpot 虛擬機中,納入字符串常量池的字符串被存儲在永久代中,因此導致了一系列的性能問題和內存溢出錯誤。想要了解這些永久代移除這些字符串的信息,請訪問這裏查看。

- 辭永久代,迎元空間

隨着 Java8 的到來,我們再也見不到永久代了。但是這並不意味着類的元數據信息也消失了。這些數據被移到了一個與堆不相連的本地內存區域,這個區域就是我們要提到的元空間。

這項改動是很有必要的,因爲對永久代進行調優是很困難的。永久代中的元數據可能會隨着每一次 Full GC 發生而進行移動。並且爲永久代設置空間大小也是很難確定的,因爲這其中有很多影響因素,比如類的總數,常量池的大小和方法數量等。

同時,HotSpot 虛擬機的每種類型的垃圾回收器都需要特殊處理永久代中的元數據。將元數據從永久代剝離出來,不僅實現了對元空間的無縫管理,還可以簡化 Full GC 以及對以後的併發隔離類元數據等方面進行優化。
在這裏插入圖片描述

- 移除永久代的影響

由於類的元數據分配在本地內存中,元空間的最大可分配空間就是系統可用內存空間。因此,我們就不會遇到永久代存在時的內存溢出錯誤,也不會出現泄漏的數據移到交換區這樣的事情。最終用戶可以爲元空間設置一個可用空間最大值,如果不進行設置,JVM 會自動根據類的元數據大小動態增加元空間的容量。

**注意:**永久代的移除並不代表自定義的類加載器泄露問題就解決了。因此,你還必須監控你的內存消耗情況,因爲一旦發生泄漏,會佔用你的大量本地內存,並且還可能導致交換區交換更加糟糕。

- 元空間內存管理

元空間的內存管理由元空間虛擬機來完成。先前,對於類的元數據我們需要不同的垃圾回收器進行處理,現在只需要執行元空間虛擬機的 C++ 代碼即可完成。在元空間中,類和其元數據的生命週期和其對應的類加載器是相同的。話句話說,只要類加載器存活,其加載的類的元數據也是存活的,因而不會被回收掉。

我們從行文到現在提到的元空間稍微有點不嚴謹。準確的來說,每一個類加載器的存儲區域都稱作一個元空間,所有的元空間合在一起就是我們一直說的元空間。當一個類加載器被垃圾回收器標記爲不再存活,其對應的元空間會被回收。在元空間的回收過程中沒有重定位和壓縮等操作。但是元空間內的元數據會進行掃描來確定 Java 引用。

元空間虛擬機負責元空間的分配,其採用的形式爲組塊分配。組塊的大小因類加載器的類型而異。在元空間虛擬機中存在一個全局的空閒組塊列表。當一個類加載器需要組塊時,它就會從這個全局的組塊列表中獲取並維持一個自己的組塊列表。當一個類加載器不再存活,那麼其持有的組塊將會被釋放,並返回給全局組塊列表。類加載器持有的組塊又會被分成多個塊,每一個塊存儲一個單元的元信息。組塊中的塊是線性分配(指針碰撞分配形式)。組塊分配自內存映射區域。這些全局的虛擬內存映射區域以鏈表形式連接,一旦某個虛擬內存映射區域清空,這部分內存就會返回給操作系統。
在這裏插入圖片描述
上圖展示的是虛擬內存映射區域如何進行元組塊的分配。類加載器 1 和 3 表明使用了反射或者爲匿名類加載器,他們使用了特定大小組塊。 而類加載器 2 和 4 根據其內部條目的數量使用小型或者中型的組塊。

- 元空間調優與工具

正如上面提到的,元空間虛擬機控制元空間的增長。但是有些時候我們想限制其增長,比如通過顯式在命令行中設置 -XX:MaxMetaspaceSize。默認情況下,-XX:MaxMetaspaceSize 的值沒有限制,因此元空間甚至可以延伸到交換區,但是這時候當我們進行本地內存分配時將會失敗。

對於一個 64 位的服務器端 JVM 來說,其默認的–XX:MetaspaceSize 值爲 21MB。這就是初始的高水位線。一旦觸及到這個水位線,Full GC 將會被觸發並卸載沒有用的類(即這些類對應的類加載器不再存活),然後這個高水位線將會重置。新的高水位線的值取決於 GC 後釋放了多少元空間。如果釋放的空間不足,這個高水位線則上升。如果釋放空間過多,則高水位線下降。如果初始化的高水位線設置過低,上述高水位線調整情況會發生很多次。通過垃圾回收器的日誌我們可以觀察到 Full GC 多次調用。爲了避免頻繁的 GC,建議將–XX:MetaspaceSize 設置爲一個相對較高的值。

經過多次 GC 之後,元空間虛擬機自動調節高水位線,以此來推遲下一次垃圾回收到來。

有這樣兩個選項 ‑XX:MinMetaspaceFreeRatio 和‑XX:MaxMetaspaceFreeRatio,他們類似於 GC 的 FreeRatio 選項,用來設置元空間空閒比例的最大值和最小值。我們可以通過命令行對這兩個選項設置對應的值。

下面是一些改進的工具,用來獲取更多關於元空間的信息。

  • jmap -clstats PID 打印類加載器數據。(-clstats 是 -permstat 的替代方案,在 JDK8 之前,-permstat 用來打印類加載器的數據)。下面的例子輸出就是 DaCapo’s Avrora benchmark 程序的類加載器數據
$ jmap -clstats 
Attaching to process ID 6476, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.5-b02
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness.liveness analysis may be inaccurate ...
class_loader classes      bytes parent_loader     alive? type 

     655  1222734     null      live   
0x000000074004a6c0    0    0    0x000000074004a708    dead      java/util/ResourceBundle$RBClassLoader@0x00000007c0053e20
0x000000074004a760    0    0      null      dead      sun/misc/Launcher$ExtClassLoader@0x00000007c002d248
0x00000007401189c8     1     1471 0x00000007400752f8    dead      sun/reflect/DelegatingClassLoader@0x00000007c0009870
0x000000074004a708    116   316053    0x000000074004a760   dead      sun/misc/Launcher$AppClassLoader@0x00000007c0038190
0x00000007400752f8    538  773854    0x000000074004a708   dead      org/dacapo/harness/DacapoClassLoader@0x00000007c00638b0
total = 6      1310   2314112           N/A       alive=1, dead=5     N/A 
  • jstat -gc LVMID 用來打印元空間的信息,具體內容如下
    在這裏插入圖片描述
  • jcmd PID GC.class_stats 一個新的診斷命令,用來連接到運行的 JVM 並輸出詳盡的類元數據的柱狀圖。
    注意:在 JDK 6 build 13 下,需要加上‑XX:+UnlockDiagnosticVMOptions 才能正確使用 jcmd 這個命令。
$ jcmd  help GC.class_stats
9522:
GC.class_stats
Provide statistics about Java class meta data. Requires -XX:+UnlockDiagnosticVMOptions.

Impact: High: Depends on Java heap size and content. 

Syntax : GC.class_stats [options] [] 

Arguments:
     columns : [optional] Comma-separated list of all the columns to show. If not specified, the following columns are shown: InstBytes,KlassBytes,CpAll,annotations,MethodCount,Bytecodes,MethodAll,ROAll,RWAll,Total (STRING, no default value)

Options: (options must be specified using the  or = syntax)
     -all : [optional] Show all columns (BOOLEAN, false)
     -csv : [optional] Print in CSV (comma-separated values) format for spreadsheets (BOOLEAN, false)
     -help : [optional] Show meaning of all the columns (BOOLEAN, false)

提示:如果想了解字段的更多信息,請訪問這裏

使用 jcmd 的示例輸出::

$ jcmd  GC.class_stats 

7140:
Index Super InstBytes KlassBytes annotations   CpAll MethodCount Bytecodes MethodAll   ROAll   RWAll   Total ClassName
    1    -1    426416        480           0       0           0         0         0      24     576     600 [C
    2    -1    290136        480           0       0           0         0         0      40     576     616 [Lavrora.arch.legacy.LegacyInstr;
    3    -1    269840        480           0       0           0         0         0      24     576     600 [B
    4    43    137856        648           0   19248         129      4886     25288   16368   30568   46936 java.lang.Class
    5    43    136968        624           0    8760          94      4570     33616   12072   32000   44072 java.lang.String
    6    43     75872        560           0    1296           7       149      1400     880    2680    3560 java.util.HashMap$Node
    7   836     57408        608           0     720           3        69      1480     528    2488    3016 avrora.sim.util.MulticastFSMProbe
    8    43     55488        504           0     680           1        31       440     280    1536    1816 avrora.sim.FiniteStateMachine$State
    9    -1     53712        480           0       0           0         0         0      24     576     600 [Ljava.lang.Object;
   10    -1     49424        480           0       0           0         0         0      24     576     600 [I
   11    -1     49248        480           0       0           0         0         0      24     576     600 [Lavrora.sim.platform.ExternalFlash$Page;
   12    -1     24400        480           0       0           0         0         0      32     576     608 [Ljava.util.HashMap$Node;
   13   394     21408        520           0     600           3        33      1216     432    2080    2512 avrora.sim.AtmelInterpreter$IORegBehavior
   14   727     19800        672           0     968           4        71      1240     664    2472    3136 avrora.arch.legacy.LegacyInstr$MOVW
…
…
1299  1300         0        608           0     256           1         5       152     104    1024    1128 sun.util.resources.LocaleNamesBundle
 1300  1098         0        608           0    1744          10       290      1808    1176    3208    4384 sun.util.resources.OpenListResourceBundle
 1301  1098         0        616           0    2184          12       395      2200    1480    3800    5280 sun.util.resources.ParallelListResourceBundle
              2244312     794288        2024 2260976       12801    561882   3135144 1906688 4684704 6591392 Total
                34.0%      12.1%        0.0%   34.3%           -      8.5%     47.6%   28.9%   71.1%  100.0%
Index Super InstBytes KlassBytes annotations   CpAll MethodCount Bytecodes MethodAll   ROAll   RWAll   Total ClassName

- 存在的問題

前面已經提到,元空間虛擬機採用了組塊分配的形式,同時區塊的大小由類加載器類型決定。類信息並不是固定大小,因此有可能分配的空閒區塊和類需要的區塊大小不同,這種情況下可能導致碎片存在。元空間虛擬機目前並不支持壓縮操作,所以碎片化是目前最大的問題。
在這裏插入圖片描述

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