Java面试题:GC垃圾回收机制

在这里插入图片描述

文章目录

什么是GC垃圾回收

GC垃圾回收(Garbage Collection的简称):当需要分配的内存空间不再使用的时候,JVM将调用垃圾回收机制来回收不再使用的内存空间。系统级线程跟踪存储空间的分配情况。并在JVM的空闲时,检查并释放那些可被释放的存储空间。

在程序运行中会不断地创建很多的对象,这些对象数据会占用系统内存,如果得不到有效的管理,内存的占用会越来越多,甚至会出现内存溢出的情况,所以需要对内存进行合理地释放,这个时候就要用到GC垃圾回收机制。

常见的垃圾回收算法

1、标记-清除算法

在这里插入图片描述

分为两个阶段:标记阶段清除阶段

标记阶段:首先标记出所有需要回收的对象。

清除阶段:统一回收所有被标记的对象。

缺点:标记和清除过程效率都不高,会产生大量不连续的内存碎片,导致无法给大对象分配内存。

2、标记-复制算法

在这里插入图片描述

分为两个阶段:标记阶段复制阶段

标记阶段:首先需要先标记出存活的对象。

复制阶段:把存活的对象都复制到一块新的空内存里去,最后将原来的内存空间清空。

复制算法是为了解决效率问题而出现的,将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。

缺点:内存缩小为了原来的一半,在对象存活率较高的场景下要进行大量的复制操作,效率很低。

3、标记-整理算法

在这里插入图片描述

分为三个阶段:标记阶段整理阶段清除阶段

标记阶段:首先需要先标记出存活的对象。

整理阶段:将所有的存活对象压缩到内存的一端。

清除阶段:把存活边界外的内存空间都清除一遍。

让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

4、分代收集算法

存活率低:少量对象存活,适合标记-复制算法:在新生代中,每次GC时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成GC。

存活率高:大量对象存活,适合用标记-清理标记-整理算法:在老年代中,因为对象存活率高、没有额外空间对他进行分配担保,就必须使用标记-清理/标记-整理算法进行GC。

现代商用虚拟机基本都采用分代收集算法来进行垃圾回收。这种算法只是将上面内容结合起来使用而已,根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。大批对象死去、少量对象存活的(新生代),使用复制算法,复制成本低;对象存活率高、没有额外空间进行分配担保的(老年代),采用标记-清理算法或者标记-整理算法

如何判断一个对象是否可以回收

  • 引用计数算法

给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。

两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。

正因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法

  • 可达性分析算法

通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是存活的不可达的对象可被回收

在这里插入图片描述

Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC Roots 一般包含以下内容:

  • 虚拟机栈中引用的对象
  • 本地方法栈中引用的对象
  • 方法区中的常量引用的对象
  • 方法区中类静态属性引用的对象

Java对象的引用类型

Java 具有强、软、弱、虚四种强度不同的引用类型。

  • 强引用

被强引用关联的对象不会被回收。

使用 new 一个新对象的方式来创建强引用。

Object obj = new Object();
  • 软引用

被软引用关联的对象只有在内存不够的情况下才会被回收。

使用 SoftReference 类来创建软引用。

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;  //使对象只被软引用关联
  • 弱引用

被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。

使用 WeakReference 类来实现弱引用。

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
  • 虚引用

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象。

为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知。

使用 PhantomReference 来实现虚引用。

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj = null;

年轻代、老年代和永久代

在这里插入图片描述

年轻代

年轻代是用来存放新近创建对象的地方。当填充年轻代时,执行垃圾收集。这种垃圾收集称为 Minor GC。年轻一代被分为三个部分——伊甸园(Eden Memory)和两个幸存区(Survivor Memory,被称为from/to或s0/s1),默认比例是8:1:1

  1. 大多数新创建的对象都位于 Eden 内存空间中。
  2. 当 Eden 空间被对象填充时,执行Minor GC,并将所有幸存者对象移动到一个幸存者空间中。
  3. Minor GC 检查幸存者对象,并将它们移动到另一个幸存者空间。所以每次,一个幸存者空间总是空的。
  4. 经过多次 GC 循环后存活下来的对象被移动到老年代(虚拟机设置值,默认阈值为15)。通常,这是通过设置年轻一代对象的年龄阈值来实现的,然后才有资格提升到老一代。

老年代

对老年代的垃圾回收称作Full GC,老年代内存包含那些经过许多轮小型 GC 后仍然存活的对象。通常,垃圾收集是在老年代内存满时执行的。对老年代的垃圾回收称作Full GC,其发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。

大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在 Eden 区和两个Survivor 区之间发生大量的内存拷贝

永久代

永久代存放JVM运行时使用的类,永久代同样包含了Java SE库的类和方法,永久代并不是Java堆内存的一部分。永久代的对象在Full GC时进行垃圾收集。

Class 在被加载的时候被放入永久区域。它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。

Jdk1.6之前: 有永久代,常量池1.6在方法区

Jdk1.7: 有永久代,但是已经逐步 “去永久代”,常量池1.7在堆

Jdk1.8及之后:无永久代,常量池1.8在元空间

JVM中对象在堆中的生命周期

在这里插入图片描述

  1. 在 JVM 内存模型的堆中,堆被划分为新生代和老年代
    • 新生代又被进一步划分为 Eden区Survivor区,Survivor 区由 From SurvivorTo Survivor 组成
  2. 当创建一个对象时,对象会被优先分配到新生代的 Eden 区
    • 此时 JVM 会给对象定义一个对象年轻计数器-XX:MaxTenuringThreshold
  3. 当 Eden 空间不足时,JVM 将执行新生代的垃圾回收(Minor GC)
    • JVM 会把存活的对象转移到 Survivor 中,并且对象年龄 +1
    • 对象在 Survivor 中同样也会经历 Minor GC,每经历一次 Minor GC,对象年龄都会+1
  4. 如果分配的对象超过了-XX:PetenureSizeThreshold(默认为15),对象会直接被分配到老年代

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值