Why Remove the Permanent Generation From Hotspot JVM
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。
PermGen 被移除,方法区移至 Metaspace,字符串常量移至 Java Heap。
- PermGen 大小是在启动时固定好的,很难进行调优。
由于 PermGen 内存经常会溢出,引发恼人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的OOM。
PermGen 为什么会内存溢出?PermGen主要用于存放 Class 和 Meta 的信息,即加载的 Class 会被放入 PermGen space 区域。
Pergen和存放Instance的Heap区域不同,所以如果你的APP加载过多 Class 就很可能出现PermGen space错误。例如,在Web服务器对JSP进行pre compile的时候就经常会发生。移除 PermGen 可以促进 HotSpot JVM 与 JRockit VM 的融合,因为 JRockit 没有永久代。
What’s Metaspace
In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace.
Advantage:
- OOM问题将不复存在,因为默认的类的元数据分配只受本地内存大小的限制,也就是说本地内存剩余多少,理论上Metaspace就可以有多大。
- take advantage of Java Language Specification property : Classes and associated metadata lifetimes match class loader’s
- Linear allocation only
- No individual reclamation (except for RedefineClasses and class loading failure)
- No GC scan or compaction
- No relocation for metaspace objects
如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器。根据JDK 8的特性,G1和CMS都会很好地收集Metaspace区(一般都伴随着Full GC)。
为了减少垃圾回收的频率及时间,控制吞吐量,对Metaspace进行适当的监控和调优是非常有必要的。如果在Metaspace区发生了频繁的Full GC,那么可能表示存在内存泄露或Metaspace区的空间太小了。
Notice
- 默认情况下,Metaspace 容量受限于本地内存(Native Memory),默认是几乎无穷大,如果 Metaspace 发生内存泄漏,可以把系统内存耗尽。
- MaxMetaspaceSize JVM Option用于限制 Metaspace 本地内存大小,如果没有设置该选项,JVM会根据程序的运行情况,进行动态调整 Metaspace 的大小。
- 如果 Metaspace 使用大小达到了 MaxMetaspaceSize,会触发Full GC回收,回收无用的类和类加载器。在完成Full GC后,会增加 Metaspace 的大小,延迟下次Full GC的时间。
- 为了控制在 Metaspace 的GC频率,调整 Metaspace 的大小是非常必要的,频繁的在 Metaspace 是类和类加载器发生内存泄漏的征兆,同时也说明你的程序的 Metaspace 大小不合适,需要进行适当地调整。
- JVM的参数 PermSize 和 MaxPermSize 会被忽略并给出警告。
JVM Options
- -XX:MetaspaceSize:初始值。垃圾回收过后,MetaspaceSize可能会扩大。
- -XX:MaxMetaspaceSize:最大值,超过此值就会触发Full GC。此值默认没有限制,取决于系统内存的大小。JVM会动态地改变此值。
- -XX:MinMetaspaceFreeRatio:在GC之后,当前剩余可用 Metaspace 占总可用 Metaspace 的百分比小于MinMetaspaceFreeRatio,那么将对 Metaspace 进行扩容,Min默认为40。
- -XX:MaxMetaspaceFreeRatio:默认为70。
Analyze CMS Case
问题描述
每次docker container启动都会触发一次CMS GC,刚开始以为是分配给container的内存太小,后来检查发现分配给contianer的内存为4G,基本排除因为分配内存太小而造成的。
每次发布应用后,应用还没有启动完毕就会触发一次CMS GC,此时老年代使用量为0K。
分析原因
1 | Java HotSpot(TM) 64-Bit Server VM (25.65-b01) for linux-amd64 JRE (1.8.0_65-b17), built on Oct 6 2015 17:16:12 by "java_re" with gcc 4.3.0 20080428 (Red Hat 4.3.0-8) |
2.133: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(1572864K)] 796919K(3932160K), 0.0577900 secs],可知老年代已经使用0K,最大可用大小为1572864K,Heap已经使用796919K,Heap最大可用大小为3932160K。
4.144: [GC (GCLocker Initiated GC) 2018-08-20T11:38:38.811+0800: 4.145: [ParNew: 2097152K->30361K(2359296K),可知年轻代变化情况2097152K->30361K,最大可用为2359296K。
1 | -server -Xmx4g -Xms4g -Xmn2g -XX:PermSize=128m -Xss256k |
注意看JVM参数,-XX:+DisableExplicitGC已经显式禁止程序调用System.gc()进行回收操作,可以排除程序主动触发的GC回收操作。
如果不是程序触发的GC回收,那就是JVM触发的,JVM在内存不够用的时候才会触发该操作。但是我们在启动的时候已经设置了-XX:PermSize=128m。
使用命令jstat -gc pid 1000查看,结果如下
1 | S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT |
MC,MU显示 perm 实际使用大小都不到50MB。再检查JDK版本,实际使用的是JDK 8,对于该版本来说,permSize和maxPermSize已经失效。
MC: Metaspace capacity (kB),MU: Metacspace utilization (kB).
参数 | 含义 | 默认值 |
---|---|---|
-XX:MetaspaceSize | Metaspace扩容时触发FullGC的初始化阈值,也是最小的阈值 | 依照系统而区别,可以在运行时查看,线上机子大概在20MB |
-XX:MaxMetaspaceSize | jdk8 MaxMetaspace上限,配置太小会触发OOM | 默认是几乎无穷大,如果Metaspace发生内存泄漏,可以把系统内存吃掉 |
因为使用的是JDK 8,配置的 XX:PermSize 无效,应用启动之后,meta区很快到达默认值(20MB),然后触发Full GC,GC完后 Metaspace 被扩容。
建议解决方案:稳定运行一段时间后通过jstat -gc pid确认 Metaspace 的值,一般情况下,设置 128MB 或者 256Mb 即可。
Reference
Metaspace 之一:Metaspace整体介绍(永久代被替换原因、元空间特点、元空间内存查看分析方法): https://www.cnblogs.com/duanxz/p/3520829.html
https://lishoubo.github.io/2018/08/25/%E4%B8%80%E6%AC%A1CMS%E9%97%AE%E9%A2%98%E6%8E%92%E9%99%A4/
https://www.sczyh30.com/posts/Java/jvm-metaspace/
Java 8-从持久代到metaspace: https://juejin.im/post/59e969ca51882561a05a3340
一次CMS问题排除: https://lishoubo.github.io/2018/08/25/%E4%B8%80%E6%AC%A1CMS%E9%97%AE%E9%A2%98%E6%8E%92%E9%99%A4/