Java 垃圾回收原理
垃圾回收
如何判断对象可以回收
可达性分析算法
Java 虚拟机的垃圾回收器根据 可达性分析算法 来探索存货的对象
扫描堆中的对象,看是否能够沿着 GC Root 对象为起点的引用链找到该对象,若找不到,则回收对象
四种引用
强引用
只有所有 GC Root 对象不通过强引用来引用该对象时,这个对象才会被垃圾回收
软引用
- 仅有软引用引用该对象时,在垃圾回收后,内存不足时会再次进行垃圾回收,释放被软引用引用的对象
- 可以配合引用队列来使用
设置堆内存为 16m:-Xmx16m,内存不足报错
public static void main(String[] args) {
int _4MB = 4 * 1024 * 1024;
List<byte[]> bytes = new ArrayList<byte[]>();
for(int i = 0; i < 5; i++) {
byte[] bytes1 = new byte[_4MB];
System.out.println(i);
System.out.println(bytes1);
bytes.add(bytes1);
}
}
使用软引用
public static void soft() throws IOException {
int _4MB = 4 * 1024 * 1024;
List<SoftReference<byte[]>> softReferences = new ArrayList<>();
for(int i = 0; i < 5; i++) {
System.out.println(i);
SoftReference<byte[]> softReference = new SoftReference<>(new byte[_4MB]);
softReferences.add(softReference);
System.out.println(softReference.get());
}
for (SoftReference<byte[]> reference : softReferences) {
System.out.println(reference.get());
}
}
使用软引用配合引用队列:软引用引用的对象被回收后,该软引用对象会被写入到引用队列中去,只需要在softReferences
中清除软引用队列中含有的对象即可
public static void clearSoft() {
int _4MB = 4 * 1024 * 1024;
List<SoftReference<byte[]>> softReferences = new ArrayList<>();
// 软引用队列
ReferenceQueue<byte[]> softReferenceReferenceQueue = new ReferenceQueue<>();
for(int i = 0; i < 5; i++) {
System.out.println(i);
// 当软引用引用的对象被回收后,该软引用会被存储到 softReferenceReferenceQueue 中
SoftReference<byte[]> softReference = new SoftReference<>(new byte[_4MB],softReferenceReferenceQueue);
softReferences.add(softReference);
System.out.println(softReference.get());
}
// 清除软引用对象
Reference<? extends byte[]> poll = softReferenceReferenceQueue.poll();
while (poll != null) {
softReferences.remove(poll);
poll = softReferenceReferenceQueue.poll();
}
for (SoftReference<byte[]> reference : softReferences) {
System.out.println(reference.get());
}
}
弱引用
- 仅用虚引用引用该对象,在垃圾回收时,不管内存是否足够,都会回收被虚引用引用的对象
- 可以配合引用队列来使用
public static void weak() {
int _4MB = 4 * 1024 * 1024;
// 创建 GC Root 对象
List<WeakReference<byte[]>> weakReferences = new ArrayList<>();
for(int i = 0; i < 5; i++) {
// 创建虚引用对象
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB]);
boolean add = weakReferences.add(weakReference);
System.out.println(weakReference.get());
}
for (WeakReference<byte[]> weakReference : weakReferences) {
System.out.println(weakReference.get());
}
}
虚引用
- 必须配合引用队列使用,主要配合 bytebuffer 使用,被引用对象回收时,会将虚引用入队,由 reference handler 线程调用虚引用相关方法释放内存
终结器引用
- 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用的对象暂时没有被回收),再由 finalizer 线程通过终结器引用找到对象并调用它的 finalize 方法,第二次 GC 时才能回收该对象
垃圾回收算法
标记清除
- 找出没有被 GC Root 引用的对象,打上标记
- 清除没有被 GC Root 引用的对象
特点:
- 速度快;
- 容易造成大量的内存碎片
标记整理
- 找出没有被 GC Root 引用的对象,打上标记
- 清除被标记的对象
- 整理内存块,减少碎片
特点:
- 速度慢
- 不存在内存碎片
复制
- 找出没有被 GC Root 引用的对象,打上标记;
- 存在两个内存块 from 和 to;
- 将没有被标记的对象从 from 移动到 to 中,接着清除 from 中的对象;
- 交换 from 和 to 的内容;
特点:
- 不会产生内存碎片;
- 占用双倍的内存空间;
分代垃圾回收
Java 中堆内存的区域划分为 新生代 和 老年代
新生代:存放生命周期较短的对象,分为伊甸园,幸存区 From,幸存区 To;
老年代:存放生命周期较长的对象,有价值,长时间存在的对象;
回收过程
- 新产生的对象放在伊甸园中,若新生代的空间不足,则会发生一次 Minor gc,会发生一次 stop the world
stop the world 暂停用户线程,因为对象的复制和内存的整理会导致某些对象的内存地址发生了改变,多个线程访问会造成混乱,等垃圾回收结束,用户线程才继续进行
- Minor gc 将伊甸园和幸存区 From 中幸存的对象复制(使用复制算法)到幸存区 To 中,然后交换幸存区 From 和 To
Minor gc 扫描的范围包括 伊甸园 和幸存区 From
- 幸存的对象会将自己的寿命加 1,若该寿命超过了阈值(15,使用 4 bit 存储),则会将该对象晋升到老年代中
- 当老年代的空间不足,则先尝试进行 Minor gc [ 因为对象是要先插入伊甸园中的 ],如果之后空间仍然不足,则会发生 Full gc,Full gc 也会产生一次 stop the world
- 如果空间仍然不足,则会产生 OutOfMemoryError
垃圾回收器
- 串行
-
- 单核 CPU
- 堆内存较少
- 吞吐量优先
-
- 多线程
- 堆内存较大
- 单位时间内,STW 的时间最短
- 响应时间优先
-
- 多线程
- 堆内存较大
- 每次 STW 的时间最短
串行垃圾回收器
在垃圾回收时:保证只有一个 GC 线程运行
GC参数:-XX:+UseSerialGC = Serial + Serialold
新生代采用复制算法,老年代采用标记整理算法
吞吐量优先垃圾回收器
多个线程进行垃圾回收
GC 参数:-XX:+UseParallelGC~-XX:+UseParallelOldGC
,新生代采用复制算法,老年代采用标记整理算法
基于响应时间的垃圾回收器
GC参数:-XX:+UseConcMarkSweepGC~-XX:+UseParNewGC ~ SerialOld
新生代采用复制算法,老年代采用标记清除算法,速度快
转载自:https://juejin.cn/post/7226307649519026231