技巧💡
JDK1.7叫 永久代 ,本质性上是一样的,都是对 JVM 规范中方法区的实现。最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存
在JDK1.8之后,属于本地内存,而不是jvm内存。
- 永久代元数据 -主要用于存储类的信息、常量池、方法数据、方法代码等
- 永久代必须指定大小限制(属于jvm内存),元数据可以设置,也可以不设置,无上限(受限与物理内存) 属于本地内存
- 演进历史
- jdk1.6: 有永久代,静态变量存放在永久代上
- jdk1.7: 有永久代,字符串常量池、静态变量保存在堆中
- jdk1.8: 无永久代, 字符串常量池、静态变量保存在堆中,元空间为堆外内存
- MethodArea逻辑概念 - 永久代、元数据
方法区的演进
-
JDK 1.6之前(有永久代,静态变量存放在永久代上)
-
JDK 1.7(有永久代,但已经逐步去永久代,字符串常量池,静态变量移除,保存在堆中)
-
JDK 1.8以及之后(无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池,静态变量仍在堆)
永久代为什么被元空间替换(永久代空间不好设置大小,怕动态加载类多,影响永久代大小)
随着 JAVA8的到来,HotSpotVM中再也见不到永久代了,但是并不意味着类的元数据信息也消失了,这些数据被转移到了一个与堆不相连的本地内存区域,这个区域叫做元空间MetaSpace
- 由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统的可用内存空间
- 为永久代设置空间大小很难确定,在某些场景下,如果动态加载类过多,就容易产生OOM
- 而元空间并不在虚拟机中,而是使用本地内存,因此默认情况下,元空间的大小仅受本地内存限制
- 对永久代进行调优是很困难的
StringTable为什么要调整?(在永久代回收效率低)
技巧💡
- 永久代默认情况下比较小,大量字符串容易导致OOM。
- 永久代垃圾回收频率低
jdk7中将StringTable放到了堆空间中,因为永久代的回收效率很低。在fullGC的时候才触发,而fullGC是老年代空间不足,永久代不足时才触发这就导致了StringTable回收效率不高,而我们开发中会创建大量的字符串,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。
方法区会进行垃圾回收吗?会,看jvm实现
有些人认为方法区是没有垃圾收集行为的,其实不然。Java虚拟机规范对方法区的约束非常宽松,提到过可以不要求虚拟机在方法区实现垃圾收集。事实上,也确实有未实现或未能完整实现方法区类型卸载的收集器,如JDK11 ZGc
回收方法区内存
方法区中存放生命周期较长的类信息、常量、静态变量,每次垃圾收集只有少量的垃圾被清除。方法区中主要清除两种垃圾:
- 废弃常量
- 无用的类
判定废弃常量
只要常量池中的常量不被任何变量或对象引用,那么这些常量就会被清除掉。比如,一个字符串 “bingo” 进入了常量池,但是当前系统没有任何一个 String 对象引用常量池中的 “bingo” 常量,也没有其它地方引用这个字面量,必要的话,“bingo”常量会被清理出常量池。
判定无用的类
判定一个类是否是“无用的类”,条件较为苛刻。
- 该类的所有对象都已经被清除
- 加载该类的 ClassLoader 已经被回收
- 该类的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
一个类被虚拟机加载进方法区,那么在堆中就会有一个代表该类的对象:java.lang.Class。这个对象在类被加载进方法区时创建,在方法区该类被删除时清除。