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 </pre></li>
- jstat -gc LVMID 用來打印元空間的信息,具體內容如下
- jcmd PID GC.class_stats 一個新的診斷命令,用來連接到運行的JVM并輸出詳盡的類元數據的柱狀圖。 </ul>
注意:在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)</pre>
提示:如果想了解字段的更多信息,請訪問這里
使用jcmd的示例輸出:
$ jcmd GC.class_stats7140: 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</pre>
存在的問題
前面已經提到,元空間虛擬機采用了組塊分配的形式,同時區塊的大小由類加載器類型決定。類信息并不是固定大小,因此有可能分配的空閑區塊和類需要的區塊大小不同,這種情況下可能導致碎片存在。元空間虛擬機目前并不支持壓縮操作,所以碎片化是目前最大的問題。
![]()
關于作者
Monica Beckwith是 一位在硬件行業有著10多年經驗的性能研究工程師。她目前在Servergy公司任性能架構師一職。該公司為一家提供高效服務器的創業公司。此 外,Monica曾在Sun,Oracle和AMD等公司致力于服務器端JVM優化。Monica還是JavaOne 2013會議的演講嘉賓。想要關注的可以在推ter上查找@mon_beck。
查看英文原文:Where Has the Java PermGen Gone?
來自:http://www.infoq.com/cn/articles/Java-PERMGEN-Removed本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!