JVM垃圾回收篇-垃圾回收算法与五种引用
引用计数法
引用计数(英语:reference counting,RC)是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程,使用引用计数技术可以实现自动资源管理的目的。同时引用计数还可以指使用引用计数技术回收未使用资源的垃圾回收算法
在早期的Python虚拟机中就采用了引用计数算法进行垃圾回收
在Python中每一个对象的核心就是一个结构体PyObject
,它的内部有一个引用计数器ob_refcnt
。程序在运行的过程中会实时的更新ob_refcnt
的值,来反映引用当前对象的名称数量。当某对象的引用计数值为0,那么它的内存就会被立即释放掉
以下情况是导致引用计数加一的情况:
- 对象被创建,例如a=2
- 对象被引用,b=a
- 对象被作为参数,传入到一个函数中
- 对象作为一个元素,存储在容器中
下面的情况则会导致引用计数减一:
- 对象别名被显示销毁 del
- 对象别名被赋予新的对象
- 一个对象离开他的作用域
- 对象所在的容器被销毁或者是从容器中删除对象
引用计数法有其明显的优点,如高效、实现逻辑简单、具备实时性,一旦一个对象的引用计数归零,内存就直接释放了
但是一旦出现循环引用,引用计数算法则无法解决这个问题
可达性分析算法
Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收
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)
-
仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象
-
可以配合引用队列来释放弱引用自身
弱引用(WeakReference)
-
仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
-
可以配合引用队列来释放弱引用自身
虚引用(PhantomReference)
- 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队(虚引用对象自身持有直接内存的地址),由 Reference Handler 线程调用虚引用相关方法释放直接内存
终结器引用(FinalReference)
- 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程(优先级很低)通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象
- 由于线程级别很低,对象可能迟迟无法回收,所以不推荐使用
示例
软引用
package com.vmware.chat2;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* @apiNote 演示软引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo2 {
private static final int _4MB = 4 * 1024 * 1024;
public static void main(String[] args) throws IOException {
softReference();
}
public static void strongReference() throws IOException {
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(new byte[_4MB]);
}
System.in.read();
}
public static void softReference() {
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());
list.add(ref);
}
System.out.println("loop end:"+list.size());
for (SoftReference<byte[]> softReference : list) {
System.out.println(softReference.get());
}
}
}
调用strongReference
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
- 出现了堆内存不足的情况
调用softReference
[B@1b6d3586
[B@4554617c
[B@74a14482
[GC (Allocation Failure) [PSYoungGen: 2100K->488K(6144K)] 14388K->13092K(19968K), 0.0007545 secs] [Times: user=0.08 sys=0.02, real=0.00 secs]
[B@1540e19d
[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17300K->17412K(19968K), 0.0004274 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 4696K->4449K(6144K)] [ParOldGen: 12716K->12666K(13824K)] 17412K->17115K(19968K), [Metaspace: 3458K->3458K(1056768K)], 0.0035200 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) --[PSYoungGen: 4449K->4449K(6144K)] 17115K->17115K(19968K), 0.0003686 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4449K->0K(6144K)] [ParOldGen: 12666K->705K(8192K)] 17115K->705K(14336K), [Metaspace: 3458K->3458K(1056768K)], 0.0041470 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[B@677327b6
loop end:5
null
null
null
null
[B@677327b6
Heap
PSYoungGen total 6144K, used 4264K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa128,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 8192K, used 705K [0x00000000fec00000, 0x00000000ff400000, 0x00000000ff980000)
object space 8192K, 8% used [0x00000000fec00000,0x00000000fecb04d8,0x00000000ff400000)
Metaspace used 3467K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 371K, capacity 388K, committed 512K, reserved 1048576K
- 可以看出当垃圾回收后内存仍然出现不足,所以软引用对象被回收
- 软引用所引用的对象变为null
引用队列
public static void softReferenceLinkQueue() {
//创建引用队列
ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
//关联引用队列,当软引用所关联的byte[]被回收时,软引用自己会被加入到引用队列中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], referenceQueue);
System.out.println(ref.get());
list.add(ref);
}
System.out.println("loop end:" + list.size());
//从引用队列中清理无用的软引用对象
Reference<? extends byte[]> reference = referenceQueue.poll();
while (reference != null) {
list.remove(reference);
reference = referenceQueue.poll();
}
for (SoftReference<byte[]> softReference : list) {
System.out.println(softReference.get());
}
}
弱引用
/**
* @apiNote 演示弱引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo3 {
private static final int _4MB = 4 * 1024 * 1024;
public static void main(String[] args) {
List<WeakReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
list.add(ref);
for (WeakReference<byte[]> reference : list) {
System.out.print(reference.get() + " ");
}
System.out.println();
}
System.out.println("loop end:"+list.size());
}
}
输出结果
[B@1b6d3586
[B@1b6d3586 [B@4554617c
[B@1b6d3586 [B@4554617c [B@74a14482
[GC (Allocation Failure) [PSYoungGen: 2100K->488K(6144K)] 14388K->13100K(19968K), 0.0006644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[B@1b6d3586 [B@4554617c [B@74a14482 [B@1540e19d
=========================================================
[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17308K->17380K(19968K), 0.0005451 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 4696K->0K(6144K)] [ParOldGen: 12684K->731K(9216K)] 17380K->731K(15360K), [Metaspace: 3456K->3456K(1056768K)], 0.0045518 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
null null null null [B@677327b6
=========================================================
loop end:5
Heap
PSYoungGen total 6144K, used 4376K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 77% used [0x00000000ff980000,0x00000000ffdc63d8,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 9216K, used 731K [0x00000000fec00000, 0x00000000ff500000, 0x00000000ff980000)
object space 9216K, 7% used [0x00000000fec00000,0x00000000fecb6ce8,0x00000000ff500000)
Metaspace used 3465K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 370K, capacity 388K, committed 512K, reserved 1048576K
- 可以看到存放第五个元素的时候触发GC对弱引用对象进行了回收