垃圾回收 笔记记录
- 1. 如何判断对象可以回收
- 1.1 引用计数法
- 1.1.1 缺点
- 1.2 可达性分析算法
- 1.2.1 可达分析、根对象
- 1.2.2 优缺点
- 1.3 四种引用(强软弱虚)
- 1.3.1 软引用的实际使用案例
- 1.3.2 软引用-引用队列
- 1.3.3 弱引用的实际使用案例
- 2. 垃圾回收算法
- 2.1 标记清除算法
- 2.2 标记整理
- 2.3 复制
- 3. 分代垃圾回收
- 4. 垃圾回收器
- 4.1 吞吐量优先
- 4.2 响应时间优先
- 5. 垃圾回收调优
1. 如何判断对象可以回收
下面介绍一些判断对象是否可以被回收的算法。
1.1 引用计数法
基本原理
- 每个对象关联一个计数器(整数),记录当前有多少引用指向它。
- 引用增加时(如被变量赋值、被其他对象引用),计数器+1。
- 引用减少时(如变量离开作用域、被显式置为null),计数器-1。
- 当计数器归零,说明对象不再被任何引用指向,立即回收其内存。
1.1.1 缺点
引用计数法虽然简单,但存在一个致命问题:无法解决循环引用。例如:
class A {B b;
}class B {A a;
}public class Main {public static void main(String[] args) {A a = new A(); // A 的引用计数 = 1B b = new B(); // B 的引用计数 = 1a.b = b; // B 的引用计数 = 2b.a = a; // A 的引用计数 = 2a = null; // A 的引用计数 = 1b = null; // B 的引用计数 = 1// 此时 A 和 B 互相引用,引用计数都不为 0,但已经无法访问,造成内存泄漏!}
}
问题:
- 即使 a 和 b 已经不再被外部引用,但由于它们互相引用,引用计数仍然 > 0,导致无法回收,造成内存泄漏。
- Java 的解决方案:采用可达性分析(Reachability Analysis),从 GC Roots(如静态变量、活动线程栈变量等)出发,标记所有可达对象,未被标记的则回收。
1.2 可达性分析算法
- 可达性分析算法(Reachability Analysis)是 Java 垃圾回收(GC)的核心算法,用于判断对象是否存活。相比引用计数法,它能有效解决循环引用问题,是现代 JVM 采用的默认策略。
- 从一组「GC Roots」出发,遍历所有能被引用的对象,未被遍历到的即为垃圾。类似于“从树根出发,标记所有可达的树枝和树叶,剩下的就是需要清理的枯枝”。
- 肯定不能当成垃圾被回收的对象称为根对象
1.2.1 可达分析、根对象
- Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象。
- 扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收。
- 哪些对象可以作为GC Root? 这里可以使用eclipse提供的MAT来找到
MAT中有一个功能就是找到当前快照中的GC Roots
GC Roots 构成:
jps 找到当前运行类的进程id
再配合jmap -dump:format=b,live,file=1.bin 21384
dump就是把堆内存当前运行的状态转储成一个文件。
format表示转储文件的格式,b是二进制格式。
live会主动触发一次垃圾回收,我们关注一次回收后还存活的对象。
file=1.bin 表示存储的文件名称。
21384就是jps看到的进程id.
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** 通过Eclipse的MAT 查看哪些是GC Roots对象* */
public class Demo {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...");}
}
1.2.2 优缺点
1.3 四种引用(强软弱虚)
- 强引用:默认的引用类型,只要强引用存在,对象就不会被 GC 回收。即使内存不足(OOM),JVM 也不会回收强引用对象,而是抛出 OutOfMemoryError。 通对象创建(如 String s = “hello”),我们平时写代码new xx()都属于强引用。
- 软引用:当 内存不足时,GC 会回收软引用对象。适合实现 内存敏感的缓存(如图片缓存)。
- 弱引用:只要发生 GC,无论内存是否充足,弱引用对象都会被回收。比软引用更短暂,适合存储 非必须的元数据。
- 虚引用:无法通过虚引用获取对象(get() 始终返回 null)。
唯一作用:对象被回收时收到系统通知(通过 ReferenceQueue)。
必须与 ReferenceQueue 联合使用。- 其实还有一种,终结器引用:是一种与对象生命周期相关的特殊机制,它通过 finalize() 方法在对象被垃圾回收(GC)前提供一次“临终拯救”机会。但因其设计缺陷,Java 9 开始已被标记为废弃(@Deprecated),并建议使用更安全的替代方案(如 Cleaner)。
四种引用的对比:
1.3.1 软引用的实际使用案例
这里设置堆内存是20m Xmx20m,对于某些图片资源非核心业务,如果用强引用进行引用就会有可能导致内存溢出。这种不太重要的资源可以在内存紧张时将内存释放掉,这种场景就可以使用软引用。
下面代码演示放入20m的对象,发现堆内存不够直接OOM了,后面的例子使用软引用就不会OOM。
private static final int _4MB=4*1024*1024;public static void main(String[] args) throws IOException {List<byte []> list=new ArrayList<>();for (int i=0;i<5;i++){list.add(new byte[_4MB]);}System.in.read();}
使用软引用:内存不足时被回收
前4个都被释放了,只有最后1个byte数组还在。
public static void soft(){// list -->SoftReference --> byte[]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(list.size());}System.out.println("循环结束:"+list.size());for (SoftReference<byte[]> softReference : list) {System.out.println(softReference.get());}}
1.3.2 软引用-引用队列
前面的演示我们发现,当使用软引用的时候,可能前4个byte数组已经被释放了,只保留了第5个在内存中。也就是最后遍历list的时候很多元素都是null了,对这些软引用对象,其实没必要保留在list中了。这种情况我们希望把软引用本身也做一个清理,既然释放了,就把引用也清除掉。因为软引用本身也要占用内存,尽管占用的相对较少。
如何清理无用的软引用呢,就需要用到引用队列。
public static void soft() {List<SoftReference<byte[]>> list = new ArrayList<>();//引用队列ReferenceQueue<byte[]> queue = new ReferenceQueue<>();for (int i = 0; i < 5; i++) {//关联了引用队列,当软引用所关联的byte[]被回收时,那么软引用会被加入到引用队列queue中去。SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);System.out.println(ref.get());list.add(ref);System.out.println(list.size());}System.out.println("循环结束:" + list.size());//获取队列中的软引用Reference<? extends byte[]> poll = queue.poll();while (poll != null) {list.remove(poll);poll = queue.poll();}System.out.println("list中剩余的:===================>");for (SoftReference<byte[]> softReference : list) {System.out.println(softReference.get());}}
这时候看到list只有未被回收的byte数组。
1.3.3 弱引用的实际使用案例
这里可以看到第10次可能是因为软弱引用本身也比较多了,发生了fullgc,前面几次没快超出堆内存时也会发发生了gc,所以又几个null值。
public static void weak() {List<WeakReference<byte[]>> list = new ArrayList<>();//引用队列ReferenceQueue<byte[]> queue = new ReferenceQueue<>();for (int i = 0; i < 10; i++) {WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB], queue);list.add(ref);for (WeakReference<byte[]> weakReference : list) {System.out.print(weakReference.get() + " ");}System.out.println();}}