8.垃圾回收
8.1.如何判断对象可以垃圾回收?
有两种判断类是不是垃圾的办法 分别是 引用计数法 和可达性分析 。
引用计数法
引用计数法有个弊端,循环引用时,两个对象的计数都为1,导致两个对象都无法被释放
例如老师视频里举的例子:
A对象引用了B对象,同时B对象引用了A对象,导致了循环引用。
可达性分析(jvm使用)
- JVM中的垃圾回收器通过可达性分析来探索所有存活的对象
- 扫描堆中的对象,看能否沿着GC Roott对象为起点的引用链找到该对象,如果找不到,则表示可以回收
可达性分析测试方法:使用jmap工具
将这个快照文件放到eslipce 的Memory Analysis里进行解析,得到
可以作为GC Root的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
8.2.五种引用
强引用
只有GC Root都不引用该对象时,才会回收强引用对象
- 如上图B、C对象都不引用A1对象时,A1对象才会被回收
软引用
当GC Root指向软引用对象时,在内存不足时,会回收软引用所引用的对象
- 如上图如果B对象不再引用A2对象且内存不足时,软引用所引用的A2对象就会被回收
弱引用
只有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用所引用的对象
- 如上图如果B对象不再引用A3对象,则A3对象会被回收
弱引用的使用和软引用类似,只是将 SoftReference 换为了 WeakReference
虚引用
当虚引用对象所引用的对象被回收以后,虚引用对象就会被放入引用队列中,调用虚引用的方法
- 虚引用的一个体现是释放直接内存所分配的内存,当引用的对象ByteBuffer被垃圾回收以后,虚引用对象Cleaner就会被放入引用队列中,然后调用Cleaner的clean方法来释放直接内存
- 如上图,B对象不再引用ByteBuffer对象,ByteBuffer就会被回收。但是直接内存中的内存还未被回收。这时需要将虚引用对象Cleaner放入引用队列中,然后调用它的clean方法来释放直接内存
终结器引用
所有的类都继承自Object类,Object类有一个finalize方法。当某个对象不再被其他的对象所引用时,会先将终结器引用对象放入引用队列中,然后根据终结器引用对象找到它所引用的对象,然后调用该对象的finalize方法。调用以后,该对象就可以被垃圾回收了
- 如上图,B对象不再引用A4对象。这是终结器对象就会被放入引用队列中,引用队列会根据它,找到它所引用的对象。然后调用被引用对象的finalize方法。调用以后,该对象就可以被垃圾回收了
- 终结器引用有可能造成终结器对象指向的对象迟迟不被回收,因为该对象只有finallize方法被调用,才会被回收,而调用fianlly对象的线程优先度很低,不建议使用。
附加:引用队列
- 软引用和弱引用可以配合引用队列
- 在弱引用和虚引用所引用的对象被回收以后,会将这些引用放入引用队列中,方便一起回收这些软/弱引用对象
- 虚引用和终结器引用必须配合引用队列
软引用和弱引用的用处
我们看一个例子:
结果:堆内存溢出
因为用的是强引用对象,此时5个4m的对象会将内存填满,导致内存溢出 gc无法将其回收。
此时我们要使用软引用对象或者弱引用对象,让GC清理时清理掉之前的软引用和弱引用指向的对象,防止内存溢出。
软引用处理
可以看到 前四个元素都被清除,就剩下最后一个元素。
相关代码:
public class Demo1 {
public static void main(String[] args) {
final int _4M = 4*1024*1024;
List<SoftReference<byte[]>> list = new ArrayList<>();
SoftReference<byte[]> ref= new SoftReference<>(new byte[_4M]);
}
}
弱引用处理
道理和软引用基本相似。
而上面的两种处理后,即使软引用对象的指向对象被删除,软引用对象本身的内存没被释放,我们需要引用队列来清除它。
相关代码:
public class Demo1 {
public static void main(String[] args) {
final int _4M = 4*1024*1024;
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
List<SoftReference<byte[]>> list = new ArrayList<>();
SoftReference<byte[]> ref= new SoftReference<>(new byte[_4M]);
Reference<? extends byte[]> poll = queue.poll();
while(poll != null) {
list.remove(poll);
poll = queue.poll();
}
}
}
8.3.垃圾回收算法
标记-清除法
定义:标记清除算法顾名思义,是指在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后垃圾收集器根据标识清除相应的内容,给堆内存腾出相应的空间
标记-整理法
复制法
复制算法过程
8.4分代回收
jvm将堆内存分为新生代 和老年代 ,新生代的幸存区用的是复制算法,老年代用的是标记-整理算法
详细过程为:
8.5.GC分析
大对象处理策略
当遇到一个较大的对象时,就算新生代的伊甸园为空,也无法容纳该对象时,会将该对象直接晋升为老年代
线程内存溢出
某个线程的内存溢出了而抛异常(out of memory),不会让其他的线程结束运行
这是因为当一个线程抛出OOM异常后,它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行,进程依然正常
8.6.垃圾回收器
|