4年前 (2020-09-25)  Java系列 |   抢沙发  640 
文章评分 0 次,平均分 0.0

Java虚拟机(JVM)使用其类的内部表示形式,其中包含每个类的元数据,如类层次结构信息、方法数据和信息(如字节码、堆栈和变量大小)、运行时常量池以及解析的符号引用和Vtables。

在过去(当自定义类装入器不那么常见的时候),类大多是“静态”的,很少被卸载或收集,因此被标记为“永久”。另外,由于类是JVM实现的一部分,而不是由应用程序创建的,因此它们被视为“非堆”内存。

对于JDK8之前的hotspotjvm,这些“永久”表示将存在于一个称为“永久生成”的区域中。这个永久的生成与Java堆相邻,并且被限制为-XX:MaxPermSize,必须在启动JVM之前在命令行上设置,或者默认为64M(对于64位缩放指针为85M)。永久一代的收集将与老一代的收集捆绑在一起,因此,无论何时,无论何时,永久一代和老一代都将被收集。您可以立即指出的一个明显问题是对‑XX:MaxPermSize的依赖性。如果您的应用程序内存超出最大值,则会出现内存超出最大值的错误。

在JDK7之前,对于hotspotjvm,内部的字符串也被保存在永久的一代中,即PermGen,这会导致大量的性能问题和OOM错误。有关从PermGen中删除内部字符串的更多信息,请参阅此处。https://javakk.com/421.html

再见PermGen,你好,元空间

随着JDK8的出现,我们不再拥有PermGen。但元数据信息没有消失,只是它所在的空间不再与Java堆相邻。元数据现在已经转移到本机内存中的一个称为“元空间”的区域。

迁移到元空间是必要的,因为PermGen确实很难调整。元数据可能会随着每次完整的垃圾收集而移动。另外,由于PermGen的大小取决于很多因素,例如类的总数、常量池的大小、方法的大小等,所以很难对PermGen进行大小调整。

此外,HotSpot中的每个垃圾收集器都需要专门的代码来处理PermGen中的元数据。从PermGen中分离元数据不仅可以无缝地管理元空间,还可以进行改进,例如简化完整的垃圾收集和将来并发地取消类元数据的分配。

Java的PermGen永久代去哪儿了?

移除永久空间对最终用户意味着什么?

由于类元数据是从本机内存中分配的,所以最大可用空间是可用系统内存的总和。因此,您将不再遇到OOM错误,并可能最终溢出到交换空间中。最终用户可以选择限制类元数据的最大可用本机空间,也可以让JVM增加本机内存以容纳类元数据。

注意:删除PermGen并不意味着类装入器泄漏问题就消失了。所以,是的,您仍然需要监视您的消耗并相应地进行计划,因为泄漏将最终消耗您的整个本机内存,从而导致交换,这只会变得更糟。

接下来介绍Metaspace及其分配:

metaspacevm现在使用内存管理技术来管理Metaspace。因此,将工作从不同的垃圾收集器移动到一个Meta SpaseVM,在meta space中执行C++中的所有工作。元空间背后的一个主题就是类及其元数据的生存期与类装入器的生存期相匹配。也就是说,只要类加载器是活动的,元数据就在元空间中保持活动状态,并且不能被释放。

我们一直在松散地使用术语“元空间”。更正式地说,每个类加载器存储区域称为“元空间”。这些元空间统称为“元空间”。每个类加载器的元空间的回收只能在其类加载器不再活动并且垃圾回收器报告为死机之后发生。在这些元空间中没有重新定位或压缩。但是元数据被扫描以查找Java引用。

元空间虚拟机通过使用分块分配器来管理元空间分配。分块大小取决于类加载器的类型。有一个全局可用的块列表。每当类加载器需要块时,它就从全局列表中获取块并维护自己的块列表。当任何类加载器死亡时,它的块被释放,并返回到全局空闲列表。块被进一步分成块,每个块保存一个元数据单元。块的块分配是线性的(指针凹凸)。块被分配出内存映射(mmapped)空间。有这样一个全局虚拟映射空间的链表,每当任何虚拟空间被清空时,它都会返回给操作系统。

Java的PermGen永久代去哪儿了?

上图显示了在mmapped虚拟空间中使用metachunk进行的元空间分配。类加载器1和3描述反射或匿名类加载器,它们使用“专用”块大小。类加载器2和4可以根据装入器中的项的数量使用较小或中等的块大小。

元空间metaspace调优和工具:

如前所述,元空间虚拟机将管理元空间的增长。但在某些情况下,您可能希望通过在命令行上显式设置-XX:MaxMetaspaceSize来限制增长。默认情况下,–XX:MaxMetaspaceSize没有限制,因此从技术上讲,Metaspace大小可能会增长到交换空间,并且会开始出现本机分配失败。

对于64位服务器类JVM,–XX:MetaspaceSize的默认/初始值为21MB。这是初始高水位线。一旦命中此水印,就会触发一个完整的垃圾回收来卸载类(当类加载器不再活动时),并重置高水位线。高水位线的新值取决于释放的元空间量。如果释放的空间不足,则高水位线上升;如果释放的空间过大,则高水位线下降。如果初始水印太低,将重复多次。并且您将能够在垃圾收集器日志中可视化重复的完整垃圾收集。在这种情况下,建议您在命令行中将–XX:MetaspaceSize设置为更高的值,以避免初始垃圾回收。

在随后的收集之后,Metaspace VM将自动调整您的高水位线,以便进一步推动下一个Metaspace垃圾收集。

还有两个选项:‑XX:MinMetaspaceFreeRatio‑XX:MaxMetaspaceFreeRatio。这些参数类似于GC FreeRatio参数,也可以在命令行上进行设置。

已修改了一些工具以帮助获取有关元空间的更多信息,这些工具列在下面:

jmap–clstats<PID>:打印类装入器统计信息。(这现在取代了-permstat,它用于打印JDK8之前的JVMs的类装入器统计信息)。运行DaCapo的Avrora基准测试时的输出示例:

$ jmap -clstats <PID>
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 

<bootstrap>     655  1222734     null      live   <internal>
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>:现在打印元空间信息,如下例所示:

Java的PermGen永久代去哪儿了?

jcmd<PID>GC.class_status统计:这是一个新的诊断命令,使最终用户能够连接到活动的JVM并转储Java类元数据的详细柱状图。

注意:对于JDK8 build 13,您必须使用‑XX:+UnlockDiagnosticVMOptions启动Java。

$ jcmd <PID> 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] [<columns>] 

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 <key> or <key>=<value> 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 <PID> 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
…<snipped>
…<snipped>
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

更多关于metaspace内存溢出排查经验可参考这篇:https://javakk.com/160.html

当前问题:

如前所述,metaspacevm使用分块分配器。根据类加载器的类型,有多种块大小。而且,类项本身的大小不是固定的,因此有可能空闲块的大小可能与类项所需的块大小不同。所有这些都可能导致分裂。元空间虚拟机(metaspacevm)还没有使用压缩,因此碎片化是目前主要关注的问题。

Java的PermGen永久代去哪儿了?

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/470.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册