前言
本篇对java的JVM线程共享内存中的方法区进行系统性的讲解。
1、方法区&元空间概念
方法区是《Java虚拟机规范》中规定的一个内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。
元空间是方法区的实现。方法区的实现,JDK1.7之前是永久代,JDK1.8之后是元空间。
2、与永久代的区别
永久代和元空间的不不同点:
1、永久代在物理上是堆的一部分,元空间内存是操作系统本地内存。
2、jdk7中将StringTable放到了堆空间中。因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发。这就导致StringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。
3、元空间的回收
元空间大小会在JAVA程序运行过程中不断在GC后进行调整:
(相关参数可以查看文章后面内容)
4、类压缩空间CompressedClassSpace
4.1、什么是类压缩空间
在 64 位平台上,HotSpot 使用了两个压缩优化技术,Compressed Object Pointers (“CompressedOops”) 和 Compressed Class Pointers。
压缩指针,指的是在 64 位的机器上,使用 32 位的指针来访问数据(堆中的对象或 Metaspace 中的元数据)的一种方式。
4.2、为什么会有类压缩空间
32 位的指针占用更小的内存,可以更好地使用缓存
4.3、类压缩空间开启及参数
涉及参数如下:
-XX:+UseCompressedOops 允许对象指针压缩。默认开启。
-XX:+UseCompressedClassPointers 允许类指针压缩。默认开启。
-XX:CompressedClassSpaceSize 类压缩空间大小,默认1G,不能大于3G。
类压缩空间开启要求
1、类指针压缩开启必须需要对象指针压缩的开启,反过来则不需要
2、对象指针压缩开启要求堆大小 小于 32G,所以类压缩空间开启需要堆大小小于32G
4.4、类压缩原理介绍
注:
A、压缩的是引用klass的指针,不是klass。
B、如果类压缩空间没有开启,那么klass数据就存放在non-klass空间。
klass和non-klass空间
一个类被加载内存分配如下:
5、参数介绍及优化建议
注:可以用 java -XX:+PrintFlagsFinal -version |grep 【参数】 开查看参数的默认值
a、MetaspaceSize
初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
个人建议可以和MaxMetaspaceSize设置成一样大。
b、MaxMetaspaceSize
最大元空间大小,默认是没有限制,最大限制就是操作系统本地内存的限制
建议:
1、最好限制元空间的最大大小,避免导致操作系统内存使用率过高,避免代码问题过晚暴露(如有类加载器出现泄露)。
2、如果启动之后,发现应用元空间GC过于频繁,应该调大一点。
c、MinMetaspaceFreeRatio
最小空闲比,当 Metaspace 发生 GC 后,会计算 Metaspace 的空闲比,如果空闲比(空闲空间/当前 Metaspace 大小)小于此值,就会触发 Metaspace 扩容。默认值是 40。
d、MaxMetaspaceFreeRatio
最大空闲比,当 Metaspace 发生 GC 后,会计算 Metaspace 的空闲比,如果空闲比(空闲空间/当前 Metaspace 大小)大于此值,就会触发 Metaspace 释放空间。默认值是 70 。
e、MinMetaspaceExpansion和MaxMetaspaceExpansion
这两个并不是直接限定元空间扩容的大小,而是为了增大触发 metaspace GC 的阈值。
MinMetaspaceExpansion默认332.8K,增大触发metaspace GC阈值的最小要求。
如果需要分配的内存小于MinMetaspaceExpansion,则将metaspace GC的阈值提升MinMetaspaceExpansion。
MaxMetaspaceExpansion默认5.2M,增大触发metaspace GC阈值的最大要求。
如果需要分配的内存大于MinMetaspaceExpansion但是小于MaxMetaspaceExpansion,那增量就是MaxMetaspaceExpansion。
如果需要分配的内存超过了MaxMetaspaceExpansion,那增量就是MinMetaspaceExpansion加上要分配的内存大小
注:每次分配只会给对应的线程一次扩展触发metaspace GC阈值的机会,如果扩展了,但是还不能分配,那就只能等着做GC了。
f、CompressedClassSpaceSize
类压缩空间大小,也就是klass space的大小,默认是1G,最好不要超过3G。
6、常见报错
6.1、java.lang.OutOfMemoryError: Metaspace
当出现这个错误的时候,很明显是加载的类过多,将元空间的内存占用完了。
第一步应该是先查看自己的-XX:MaxMetaspaceSize设置的是否合适,可以适当的增加,然后观察应用是否还会出现。这个是治标不治本,但是是见效最快的。
但是如果发现问题依然存在,而-XX:MaxMetaspaceSize的值已经不可以设置再大的时候,需要进行更深一步的排查。主要是如下两个方向进行排查:
A、引用的jar包加载了很多的class
排查应用引入的较大的jar包,去除掉多余的加载项,按照最小颗粒度加载。
B、动态生成类过多
Java的一些框架会使用动态代理技术等动态生成类(例如spring),使用这样方式创建对象可能会创建大量的类,导致元空间被占满。
6.2、java.lang.OutOfMemoryError: Compressed class space
元空间本身可以不设置MaxMetaspaceSize,从而让元空间大小没有限制,只受到本地内存大小限制。
但是了类压缩空间是有限制的,就算不设置CompressedClassSpaceSize,一样会有1G的限制大小。
分析方向和java.lang.OutOfMemoryError: Metaspace一样。
7、元空间分析方法
这里只讲对应工具或者命令分析该知识点的方法,具体命令和工具会在后续文章单独讲解
7.1、jinfo命令
查看参数配置
jinfo -flag MetaspaceSize 【PID】
7.2、jstat 命令
查看元空间使用百分比
jstat -gcutil 344537 | column -t
查看元空间当前总大小及使用大小
jstat -gc 【PID】| column -t
MC是当前元空间大小
MU是当前元空间使用大小
CCSC是压缩类空间大小
CCSU是压缩类空间使用大小
元空间统计
jstat -gcmetacapacity 【PID】| column -t
MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
7.3、arthas工具
查看各个类加载器加载情况
classloader
numberOfInstances:classloader的实例数量
loadedCountTotal:该实例加载类的数量
按类加载实例查看统计信息,可以看到类加载的hashcode
classloader -l
查看类加载的继承关系
classloader -t
查看类加载器实际所在位置