判断对象是否已死:
- 引用计数法(很难解决对象之间相互循环引用的问题)
- 可达性分析算法
- 可作为GC Roots的对象包括:
① 虚拟机栈中引用的对象
② 方法去中类静态属性引用的对象
③ 方法区中常量引用的对象
④ 本地方法栈中JNI(Native方法)引用的对象
引用
- 强引用
- 软引用
- 弱引用
- 虚引用
回收区域
1. 方法区:回收性价比低
2. 堆(主要回收区域):
- 新生代:Minor GC(复制算法)
- 老年代:Major GC/Full GC(标记—整理算法)
- 永久代:废弃常量和无用类
无用类:
① 该类的所有实例已被回收
② 加载该类的ClassLoader已被回收
③ 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域. 它和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出Out of Memory异常。
在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。
不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入java堆中. 这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制.
采用元空间而不用永久代的几点原因:
1、为了解决永久代的Out of Memory问题,元数据和class对象存在永久代中,容易出现性能问题和内存溢出。
2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出(因为堆空间有限,此消彼长)。
3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4、Oracle 可能会将HotSpot 与 JRockit 合二为一。
垃圾收集算法
1. 标记—清除算法(最基本)
2. 复制算法(新生代)
- 新生代分为:Eden和两个Survivor大小比例是8:1,回收时,将Eden和survivor中还存活的对象一次性的复制到另一个Survivor空间中 ,同时把这些对象的年龄+1(默认情况下15岁就直接送到老年代了),最后清理掉Eden和刚才用过的Survivor空间。
- 如果Surviovr的空间不够,需要依赖其他内存(老年代)进行分配担保(虚拟机回检查老年代最大可用的连续空间是否大于新生代所有对象的总空间),这些对象直接通过分配担保机制进入老年代。(正常的需要Age达到15才可以进入老年代),如果担保失败,且不允许,则老年代需要进行一次Full GC。
3. 标记—整理算法(老年代)
垃圾收集器
内存分配
- 对象优先在Eden分配
- 大对象直接进入老年代(大对象指很长的字符串以及数组)
- 长期存活的对象进入老年代
- 虚拟机给每个对象一个对象年龄(Age)计数器。对象每经过一次Minor GC Age就加一。(默认是15岁就会进入老年代)
- 如果Survivor中相同年龄的所有对象的总和大于空间的一半,则年龄大于或等于该年龄的对象都可以直接进入老年代,无须等到要求的年龄。