Java垃圾回收机制总结

Java垃圾回收机制

垃圾回收(GC,Garbage Collection)是 Java 虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象所占据的内存空间的一种机制。注意:回收只是清理“垃圾”占用的内存空间而非对象本身。

发生地点:一般发生在堆内存中,因为大部分对象都储存在堆中。

发生时间:程序空闲时间不定时回收。

首先理解引用的概念:如果Reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。在Java中,类型可分为两大类:值类型与引用类型。值类型就是基本数据类型(如int,double 等),而引用类型,是指除了基本的变量类型之外的所有类型(如通过class定义的类型)。所有的类型在内存中都会分配一定的存储空间(形参在使用时也会分配存储空间,方法调用完成后,这块存储空间自动消失), 基本的变量类型只有一块存储空间(分配在stack中), 而引用类型有两块存储空间(一块在stack,一块在heap;从stack指向heap内存中的位置)。引用分为4种类型:

  1. 强引用(Strong Reference):如“Object obj = new Object()”,这类引用在 Java 程序中最普遍。只要强引用还存在,垃圾回收器就永远不会回收掉被引用的对象。

  2. 软引用(Soft Reference):用于描述一些可能还有用,但并非必须的对象。可用来实现内存敏感的高速缓存(如网页缓存、图片缓存等),能防止内存泄露。在系统内存不够用时,这类引用关联的对象将被垃圾回收器回收。

  3. 弱引用(Weak Reference):也是用于描述非须对象,但它的强度比软引用更弱些,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

  4. 虚引用(Phantom Reference):最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的只是为了能在这个对象被垃圾回收器回收时收到一个系统通知

那么如何找到被当作垃圾而回收的对象?

首先明确JVM内存区域划分:

内存区域分为程序计数器、虚拟机栈、本地方法栈和堆、方法区。其中程序计数器和栈都是线程私有的,其生命周期与线程相同,因此无需考虑垃圾回收;而堆和方法区是所有线程共享的,需要考虑动态分配的问题,因此有垃圾回收机制。

接下来需要判断堆和方法区中哪些对象是”垃圾“。这里使用两种算法:

  1. 引用计数法(Reference Counting Collector):为对象添加引用计数器,每增加一个引用,计数器+1;每减少一个引用,计数器-1。当计数器值为0,则判定该对象”死亡“。该方法存在的问题是:对象之间循环引用时,计数器永远不可能为0,导致该内存永不可能被回收。

  2. 根搜索算法(Tracing Collector):也称可达性分析算法。该方法是C#和Java主用的判定算法。先选定一系列根对象(GC Roots)作为起始节点集合,再从各起始节点开始从上至下搜索连接的对象是否可达,若不可达,则标记为垃圾。

    Java中被选为GC Roots的对象有以下固定来源:

    1. 虚拟机栈中的引用对象(临时变量、局部变量等);

    2. 方法区中类静态属性引用的对象(静态变量等);

    3. 方法区中常量引用的对象(字符串常量池等);

    4. 本地方法栈JNI(Native方法)引用的对象;

    5. JVM内部引用(基础数据类型、系统类加载器、常见异常对象(OOM等)等);

    6. 同步锁(sychronized关键字)持有的对象;

    7. 反映Java虚拟机内情况的JMXBean、本地代码缓存等;

    除此之外,还有一些”临时性“对象会被加入其中。

在确定垃圾回收的对象之后,需要使用算法进行垃圾回收的操作。Java垃圾收集器使用分代收集的方式处理垃圾对象。分代收集基于的思想是:不同的对象生命周期是不一样的,因此可以将不同生命周期的对象分代,不同的代采取不同的回收算法进行垃圾回收(GC),以提高回收效率。Java堆被大致划分为两大区域:

  1. 新生代(Young):主要存放新生对象,一般占据堆空间的1/3。新生代中的大部分对象都是朝生夕死,所以在新生代中会频繁的进行MinorGC。新生代又细分为Eden区和Survivor区;而Survivor区又分为SurvivorFrom(S0)区SurvivorTo(S1)区。三区默认比例为8:1:1。

  2. 老年代(Old):主要存放生命周期长的内存对象。老年代比较稳定,不会频繁进行MajorGC。而在MajorGC之前会先进行一次MinorGC,让新生对象进入老年代而导致空间不够再触发MajorGC;同时,当无法找到足够大的连续空间分配给新创建的较大对象时,也会提前触发一次MajorGC以腾出空间。

另外,还有一个永久代(Perm)的概念。永久代是指永久保存区域,是保存在方法区中的,在Java8中已被彻底移除,取而代之的是”元空间“。元空间和永久代都是对JVM规范中的方法的实现,它们之间最大的区别在于:元空间并不在JVM中,而是使用本地内存。因此,默认情况下元空间的大小仅受本地内存限制。类的元数据放入Native Memory,那么加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制,解决内存溢出问题(OOM)且降低GC复杂度。

根据垃圾收集区域的不同,主要分为以下几种垃圾回收方式:

  1. PartialGC:局部收集。其又分为:

    1. MinorGC:新生代收集

    2. MajorGC:老年代收集

    3. MixedGC:混合收集(新生代及部分老年代)

  2. FullGC:整堆收集,目标是整个Java堆和方法区。

根据前面介绍的判定垃圾的两种算法(引用计数法、可达性分析算法),垃圾回收的算法也分为两种:引用计数式垃圾回收(Reference Counting GC)、追踪式垃圾回收(Tracing GC)。由于JVM使用的是可达性分析算法,因此介绍该算法对应的三种追踪式垃圾回收算法:

  1. 标记-清除算法(Mark-Sweep):该算法是垃圾回收算法的基础。分为“标记”和“清除”两个阶段:首先标记出所需回收的对象(即可达性分析算法标记的对象),在标记完成后统一回收掉所有被标记的对象。

    优点:实现简单。

    缺点:① 执行效率不稳定。随着对象不断增多,该算法标记-清除的效率就会不断下降,造成执行效率不稳定(例如新生代会频繁GC,就不适合这种算法);② 内存空间碎片化。标记-清除后会产生大量不连续的内存碎片,导致需要分配大对象时找不到合适的空间,从而提前或频繁触发GC。

    应用:CMS。该垃圾收集器的收集区域是老年代

  2. 标记-复制算法(Mark-Copy):将内存按容量分为大小相等的两块,每次只使用其中的一块(对象面),当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面(空闲面),然后再把已使用过的内存空间一次性清理掉。

    该算法主要应用于新生代:每次使用Eden和S0用于新生对象的存储,当空间使用完后,进行垃圾回收。即把Eden、S0中存活的对象复制到S1上,然后清除Eden、S0中所有可回收对象,将原先的S0变为S1,S1变为S0。将此时存活下来的对象年龄设置为1,以后这些对象每在Survivor区熬过一次GC,年龄就+1,当对象年龄达到某个年龄(默认值为15)时,就会被移到老年代中。

    优点:① 解决了“标记-清除算法”的执行效率不稳定问题;② 解决了“标记-清除算法”的空间碎片化问题。因为是一起清空Eden和S0,所以不会再有不连续的空间碎片。

    缺点:① 因为必须有一部分空间时刻空闲着,所以会有一定的空间浪费;② 极端情况下,S1区域不一定能存储得下新生代存活下来的对象,所以需要分配担保策略(新生代内存不足时,把新生代的存活对象分配到老年代,然后将新生代腾出的空间拿给最新的对象。此时老年代需要担保是否需要执行FullGC才能装下新生代)。

    应用:Serial、ParNew、Parallel Scavenge。这三款垃圾收集器收集目标都是新生代

  3. 标记-整理算法(Mark-Compact):让所有标记的可达对象都向内存的一端移动,然后直接清理掉端边界以外的内存。

    优点:① 解决了“标记-清除算法”的空间碎片化问题;② 解决了“标记-复制算法”需要分配担保的问题。

    缺点:根据强分代假说“熬过越多次垃圾收集的对象,越难以被回收”,老年代中的大部分对象都是年龄达到了16的对象,都是很难被回收的,所以采用“标记-整理算法”去移动对象,会对应用程序的吞吐量影响很大。

    应用:Serial Old、Parallel Old。这两款垃圾收集器的收集目标都是老年代

接下来介绍垃圾回收器,7款经典垃圾回收器间的组合关系如下:

(其中,两个回收器间有连线,说明它们可以搭配使用;红色虚线:这两组组合在JDK9中被完全移除;绿色虚线:JDK14中已被删除)

垃圾回收器按执行机制可以划分为四种类型:

  1. 串行(Serial)垃圾回收器:单线程串行回收。使用标记-复制算法。在进行垃圾回收时,其他工作线程会暂停(STW)。

  2. 并行(Parallel)垃圾回收器:Serial的多线程版本。其他和Serial相同,在垃圾回收线程执行时,用户线程还是会暂停。

  3. 并发标记扫描(CMS,Concurrent-Mark-Sweep)垃圾回收器:垃圾回收线程和用户线程可以同时工作。使用标记-清除算法。

  4. G1(Garbage First)垃圾回收器:把堆内存分割成很多不相关的区域(region,物理上不连续),使用不同区域来表示Eden,Survivor和老年代。跟踪各region里垃圾回收的价值大小(回收所获得的空间大小及所需时间的经验值),在后台维护一个优先列表,每次根据允许收集时间,优先回收价值最大的region。

参考(侵删):

  1. 牛客面经

  2. 《JVM之垃圾回收过程》JVM之垃圾回收过程 - 知乎

  3. 《Java引用详解》java引用详解 - 知乎

  4. 《Java四种引用》Java四种引用_Penguin——科波特的博客-CSDN博客_java 引用

  5. 《JVM中的堆的新生代、老年代、永久代详解》JVM中的堆的新生代、老年代、永久代详解_xiaokanfuchen86的博客-CSDN博客_新生代老年代永久代

  6. 《标记整理和复制算法的区别》https://www.csdn.net/tags/MtjaAg1sNjMzNTMtYmxvZwO0O0OO0O0O.html

  7. 《Java 经典垃圾回收器详解》Java 经典垃圾回收器详解 - 知乎

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海苔小饼干

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值