万字概览 Java 虚拟机 (3)

这里的逻辑是将每个年龄段的对象占用空间进行累加,如果超过了 desired_survivor_size 则重新计算这个阈值,取这个阈值和配置的 MaxTenuringThreshold 中的最小值作为最新的 MaxTenuringThreshold。

desired_survivor_size 就是 survivor 区空间的一半。如果开启了 PrintTenuringDistribution 则会在 GC 日志中打印最新的 MaxTenuringThreshold。

PSGC 比较特殊,它通过 InitialTenuringThreshold 参数控制这个阈值,默认是 7,但每次 YGC 结束后也会重新计算阈值。

对象从 Young 区晋升到 Old 区失败

当对象从 Young 区晋升到 Old 区时,如果 Old 区空间不足以容纳本次晋升对象所需要的空间就会晋升失败,并会导致触发一次 Full GC。JVM 并不总是等到 YGC 完成后要执行晋升时才做这个判断,而是在 YGC 发生之前就进行判定。根据多次晋升的数据统计出历次平均晋升对象的大小,如果当前 Old 区还有足够空间容纳,则直接进行 YGC,否则直接进行 Full GC 让 Old 区腾出足够的空间容纳将要晋升对象。

Method Area

方法区是 JVM 规范中的一块区域,在 JDK8 之前使用永久代(Permanent Generation)实现方法区,从 JDK8 开始改用元数据区(Metaspace)来实现。所以「方法区」只是一种规范,而永久代和元数据区是对这个规范的一种实现。

Metaspace 被实现在 Native Memory 中,如果不限制最大使用内存,则除非系统内存耗尽,Metaspace 将无限增长。

为什么要将永久代替换为元数据区

根据官方给出的[说明](JEP 122: Remove the Permanent Generation),这是为了促进 Oracle JRockit VM 和 Hotspot VM 融合而做出的选择,JRockit 本身没有永久代。

Motivation

This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.

从实际使用中来看,永久代和元数据区主要存在以下一些区别:

内存空间设置
在 JDK8 之前,我们通过 -XX:PermSize 和 -XX:MaxPermSize 来配置永久代的初始和最大容量。但是这个容量应该定多大其实是很难的,永久代里面有类元信息、常量池等各种数据,配置太小容易溢出、太大则浪费(尽管不会一次性 Commit 这么多内存)。永久代是紧邻 Heap 的内存区域,它使用的是 JVM 进程所拥有的内存。而元数据区是在 Native Memory 中分配,理论上可用最大内存就是操作系统的可用内存,也可以通过参数 -XX:MaxMetaspaceSize 来设置最大可用内存。相比于永久代,这是一种变通方法,以理论上无限大的空间来替代固定大小空间可能导致的空间不足或者空间浪费问题。

GC 触发
永久代只有在空间耗尽导致无法分配新的对象时才会被动触发一次 Full GC;而元数据空间会动态调整参数 -XX:MetaspaceSize 的值,每当元数据空间使用内存达到这个阈值时就会主动触发 Full GC。主动触发可以避免被动触发时对象过多扫描困难和垃圾过多清理太慢的问题。

不适应现代应用的特点
永久代出现时,动态技术还不成熟和普及,永久代的空间足以容纳那时候的应用系统产生的方法区数据;而随着 JavaEE 和动态化技术的出现,永久代使用具有大小上限的空间来存储数据就显得捉襟见肘了。这一点可以参考第一点的说明。

当然,Metaspace 是否真的比永久代更优秀目前还没有一个统一的定论,也有很大一部分人认为 Metaspace 不仅没有解决永久代的问题,还增加了 JVM 方法区的实现难度。

Metaspace 的内存布局

万字概览 Java 虚拟机

从上图可以看到,Metaspace 主要由 Klass Metaspace 和 NoKlass Metaspace 两部分组成。顾名思义,Klass Metaspace 就是用来存储 Klass 的空间。

什么是 Klass ?

Klass 是字节码在 JVM 中的运行时数据结构,只会存放在 Metaspace 中。而我们通常使用 .class 属性或者 getClass() 方法获取到的是 java.lang.Class 的对象,这个对象是存在于 Heap 中的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygjjy.html