JVM垃圾回收篇-垃圾回收算法与五种引用
引用计数法
引用计数(英语:reference counting,RC)是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程,使用引用计数技术可以实现自动资源管理的目的。同时引用计数还可以指使用引用计数技术回收未使用资源的垃圾回收算法
在早期的Python虚拟机中就采用了引用计数算法进行垃圾回收
在Python中每一个对象的核心就是一个结构体PyObject
,它的内部有一个引用计数器ob_refcnt
。程序在运行的过程中会实时的更新ob_refcnt
的值,来反映引用当前对象的名称数量。当某对象的引用计数值为0,那么它的内存就会被立即释放掉
以下情况是导致引用计数加一的情况:
- 对象被创建,例如a=2
- 对象被引用,b=a
- 对象被作为参数,传入到一个函数中
- 对象作为一个元素,存储在容器中
下面的情况则会导致引用计数减一:
- 对象别名被显示销毁 del
- 对象别名被赋予新的对象
- 一个对象离开他的作用域
- 对象所在的容器被销毁或者是从容器中删除对象
引用计数法有其明显的优点,如高效、实现逻辑简单、具备实时性,一旦一个对象的引用计数归零,内存就直接释放了
但是一旦出现循环引用,引用计数算法则无法解决这个问题
可达性分析算法
Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收
Eclipse Memory Analyzer
Eclipse Memory Analyzer 提供了一个工具包用例分析Java堆转储,主要应用领域事分析内存不足错误和高内存消耗
案例一
public class Demo1 {
public static void main(String[] args) throws IOException {
List<Object> list = new ArrayList<>();
list.add("a");
list.add("b");
System.out.println(1);
System.in.read();
list = null;
System.out.println(2);
System.in.read();
System.out.println("end");
}
}
-
使用
jps
定位进程 -
抓取内存快照并转储为二进制文件
jmap -dump:format=b,live,file=1.bin PID
- format:指定格式
- b:二进制
- live:抓取存活对象,并在快照抓取前自动触发一次gc
- file:转储文件名
查看GCRoot
-
System Class:被bootstrap或者系统类加载器加载的类
-
JNI Global: native代码里的global变量
-
Busy Monitor:被调用了wait()或者notify()或者被synchronized同步的对象,如果是synchronized方法,那么静态方法指的类,非静态方法指的是对象
-
Thread:已经启动并且没有stop的线程
-
活动线程运行中局部变量所引用的对象可以作为GCRoot对象
-
方法参数引用的对象也是GCRoot对象
将list的引用置空前
- 可以看到堆中的ArrayList对象
将list的引用置空后
- 由于jmap的live参数触发了一次gc,此时GCRoot对象中的ArrayList对象已经不存在
五种引用
强引用
- 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
软引用(SoftReference)
-
仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象
-
可以配合引用队列来释放弱引用自身
虚引用(PhantomReference)
- 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队(虚引用对象自身持有直接内存的地址),由 Reference Handler 线程调用虚引用相关方法释放直接内存
终结器引用(FinalReference)
- 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程(优先级很低)通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象
- 由于线程级别很低,对象可能迟迟无法回收,所以不推荐使用