关于Java垃圾回收机制

1.概述

最近在看《深入Java虚拟机》一书,书中讲到关于垃圾回收这块很详细,于是自己做一个关于垃圾回收这块的读书总结。
Java开发一定对Java垃圾回收机制不陌生,面试的时候也会被问到GC相关的问题。那么你是否真的理解GC呢?
不了解GC机制之前肯定有以下这样的疑问:

  • 那些对象会被回收?
  • 什么时候回收?
  • 如何回收?

2.判断对象是否存活的方法

首先Java回收机制主要作用在Java堆区(方法区也会有GC),因为java堆里面存放着几乎所有的对象实例。在进行垃圾回收之前,要先判断这些对象那些是“存活”的,那些是已经“死去”的。

2.1.引用计数法

概念:给对象添加一个引用计数器,每当有个地方引用它时,计数器就加1;当引用失效时,计数器减1;任何时刻计数器为0的对象就是不可能在被使用的。
这样看来引用计数法:实现简单,判定效率也很高。
但是如果对象出互相循环引用的问题,那么就无法通知GC收集器回收它们。如:

public class Test{
	public Object mObject = null;
	public static void testGC(){
		Test testA = new Test();
		Test testB = new Test();
		testA.mObject = testB;
		testB.mObject = testA;
	}
}
2.2.可达性分析算法

在Java的主流实现中,都是通过可达性分析算法来判断对象是否存活的。
概念:通过一系列的“GC Roots”的对象作为起始点,从该节点向下搜索,搜索所走的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如下图:
可达性分析算法判断对象是否回收
那么Java中那些可以作为GC Roots对象呢?

  • Java虚拟机栈中被引用的对象,各个线程调用的参数、局部变量、临时变量等。
  • 方法区中类静态属性引用的对象,比如引用类型的静态变量。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(即一般说的Nativite方法)引用的对象。
  • 被同步锁(synchronized)持有的对象。
2.3.关于引用
  • 强引用就是值程序代码普遍存在的,只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象。
  • 软引用在系统将要发生内存溢出异常之前,将会把这些的一些列进回收范围之中进行第二次回收。如何这次回收还是没有足够的内存,就会抛出内存溢出异常。
  • 弱引用对象只能生存到下一次垃圾回收之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收被弱引用关联的对象。
  • 虚引用是最弱的一直引用关系,我们是无法通过虚引用来获取一个对象实例的,使用虚引用关联对象的唯一目的就是能在对象被回收时收到一个系统通知。
2.4.什么样的对象才是真正死亡

即使在可达性分析中不可达对象,也并非是非死不可的。要真正宣告对象死亡,至少要经历两次标记过程。

  1. 第一次标记:可达性分析算法中对象不可达。
  2. 第二次标记:对象没有覆盖finalize()方法或者finalize()已经被虚拟机调用过。(注意:任何对象的finalize()方法都只会被系统自动调用一次,并且我们尽量别去重写finalize()方法。)

3.垃圾回收算法

3.1.标记-清除(Mark-Sweep)算法

是最基础的收集算法,算法和它名字一样,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
它有两个不足:

  1. 效率问题,标记和清除两个过程效率都不高
  2. 标记清除后会产生大量不连续的内存碎片,空间碎片过多导致以后分配较大对象时,没有足够的连续内存存储,导致提前触发另一次GC。
    标记-清除算法执行过程如下图:
    标记-清除
3.2.复制(Copying)算法

为了提升标记清除算法效率,复制算法出现了。复制算法:将可用内存按容量大小划分为大小相等的两块,每次只使用其中一块,当一块内存用完了,就将还存活的对象负责到另一块上面去,然后把已使用过的内存空间一次全部清理掉。
不足:

  1. 将内存缩小为原来的一半。
  2. 对象存活率较高时,就要进行较多次的复制操作,效率会降低。(所以新生代内存区采用复制算法,因为新生代中对象98%是“朝生夕死”的)
    复制算法法执行过程如下图:
    复制算法
3.3.标记-整理(Mark-Compact)算法

标记-整理算法和标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
复制算法法执行过程如下图:
标记-整理算法

3.4.分代收集(Generational Collection)算法

当前商业虚拟机的垃圾收集都采用“分代收集”,这种算法并没有什么新思想。只是根据对象存活周期的不同将内存划分为几块(一般是把java堆划分为新生代、老年代),针对不同生命周期的对象可以采取不同的回收方式,以便提高回收效率。有时候还会提到永久代,永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间。

4.JVM内存分配

JVM内存主要由新生代、老年代、永久代构成。

4.1.新生代

新生代中将内存划分为一块较大的Eden空间和两块较小的Survivor空间(默认比例 8:1:1),每次使用Eden和其中一块Survivor。大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间。如果另一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象时,这些对象会直接通过分配担保机制进入年老代。
新生代

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。

在新生代中每次垃圾回收都会发现大批对象死去,只有少量存活,选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。

4.2.老年代
4.2.1. 大对象直接进入老年代

大于3M的对象,如:很长的字符串和数组。

4.2.2. 长期存活的对象将进入老年代

如果对象在Eden出生并经历第一次Minor GC后仍然存活,并且能被Survivor所容纳的话,将被移动到Survivor空间中,此时对象年龄设置为1岁。对象在Survivor区中每“熬过”一次Minor GC,年龄都会加1岁,当年龄增加到年龄阈值(默认15)将会进入老年代。

4.2.3. 动态对象年龄判定

如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代,无需达到年龄阈值。

4.2.4. 空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可连续空间是否大于新生代所有对象空间总和,如果条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置值不允许冒险,那这时也要进行一次Full GC。
在这里插入图片描述
如果Eden存活下来的对象大小大于Survivor空间大小,对象会直接通过分配担保机制直接进入年老代。前题是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会存活下来在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均值大小作为经验值,与老年代剩余空间进行比较,决定是否进行Full GC来让老年代腾出空间。

老年代FC(Major GC/Full GC):指发生在老年代的GC,出现Major GC至少会伴随一次Minor GC(但非绝对)。Major GC速度一般比Minor GC慢10倍以上。
Major GC等价Full GC会对整个java堆进行垃圾收集操作。

年老代中因为对象存活率高、没有额外窘境对他进行分配担保,就必须使用“标记-清除”或者“标记-整理”算法进行回收。

4.3.永久代

很多人认为方法区(或者当代主流虚拟机(HotSpot虚拟机)中的永久代)是没有垃圾收集的,Java虚拟机规范也确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区进行垃圾收集“性价比”一般比较低。
永久代主要回收两个部分:废弃常量和无用的类。

5.JVM发生GC时,为什么会出现卡顿?(GC停顿

GC会导致堆内存块状整理,堆内存的整理必须暂停所有Java执行线程,禁止分配对象空间。
在垃圾回收过程中经常涉及到对对象的挪动(比如新生代内存中的对象在Survivor 0和Survivor 1之间的复制),进而导致需要对对象引用进行更新。为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为“Stop-The-World”。

6.垃圾收集器

如果垃圾回收算法是内存回收的方法论,那么垃圾回收集器就是内存回收的具体实现。
不同的垃圾回收器,适用于不同的场景。常用的垃圾回收器:

  • 串行(Serial)回收器是单线程的一个回收器,简单、易实现、效率高。
  • 并行(ParNew)回收器是Serial的多线程版,可以充分的利用CPU资源,减少回收的时间。
  • 吞吐量优先(Parallel Scavenge)回收器,侧重于吞吐量的控制。
  • 并发标记清除(CMS,Concurrent Mark Sweep)回收器是一种以获取最短回收停顿时间为目标的回收器,该回收器是基于“标记-清除”算法实现的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值