Java虚拟机垃圾回收过程及垃圾收集器

一、GC需要完成的事情

  1. 哪些内存需要回收?
  2. 什么时候回收?
  3. 如何回收?

垃圾回收器所关注的是Java堆和方法区。

二、哪些内存需要回收?

Java堆中存放着Java世界中几乎所有的对象实例,垃圾回收器在进行回收前要做的事情就是确定这些对象哪些还“存活”着,哪些已经“死去”(不可能再被任何途径使用的对象),垃圾收集器将保留“存活”的对象,回收“死去”的对象的内存。

判断对象是否存活的算法:

1、引用计数法

在创建对象时,为对象创建一个伴生的引用计数器,当有其他对象引用该对象时,将引用计数器的值加1,如果其他对象不再引用该对象就将引用计数器的值减1,所以当引用计数器的值为0时,就代表不再有任何对象引用该对象,就说明该对象已经”死亡“,就可以被判定为待回收。

引用计数这种判定方法,实现比较简单,判定的效率也比较高,但是主流的虚拟机里面都没有选择这种方式。假如对象a和对象b相互进行了引用,其余地方并没有对两个对象的引用了,但是两块内存的引用计数并不会为0,所以不会将两块内存划分为待回收的内存。由于以上原因,大部分的虚拟机并不会采用这种方式进行对象”死亡“状态判断的方法。

2、可达性分析法

基本思想是定义一个GC Root对象,以GC Root为起点,向下搜索,走过的路径称作引用链,当一个对象到GC Root之间没有任何的引用链可以连接时,表示这个对象可以被回收了。
在这里插入图片描述

三、垃圾收集算法

在JVM中,进行垃圾回收主要有以下几种方式:

  1. 标记-清除算法
  2. 复制算法
  3. 标记-整理算法
  4. 分代收集算法

1、标记-清除算法

标记-清除算法是最基础的收集算法,该算法分成两个过程:标记过程和清除过程。

首先标记出所有需要收集的对象,在标记完成后触发垃圾回收,标记过程就是上面的两种方法。

这种算法存在两个明显的问题:
第一个问题是效率问题。在这种算法中,标记和清除阶段的效率都不是很高。
第二个问题就是空间问题,这种算法在标记清除之后会产生大量的内存碎片,也就是大量的可使用的小空间,但是这种小空间数量如果太多会导致程序在为大对象分配内存空间时无法找到合适的内存,从而会触发另外一次垃圾回收过程。

2、复制算法

由于标记-整理算法所存在的效率和空间问题,产生了第二种算法:复制算法。
原理是将内存划分为相等的两块,每次只使用其中的一块,在第一块内存使用完之后,触发一次垃圾回收,将还存活的对象复制到另外一块内存中去,然后将第一块已使用的内存全部清除掉,之后的内存分配发生在第二块中,当第二块使用完成后,继续重复上述步骤。

这样就不需要考虑内存碎片问题,而且每次只回收其中的一块内存,也让效率得到了提高。但是这种算法的代价就是将内存分成了两块,每次只能使用其中一块,所以在后来的分代算法中,只使用这种算法来回收新生代,而且两块内存的分配也不是各占一半。

3、标记-整理算法

复制算法虽然解决了一些问题,但是在对象存活率较高的情况下就需要进行较多的复制操作,效率会变低,更重要的是如果我们不愿意浪费一半的空间,就需要分配额外的空间来做内存担保,以应对使用的内存中对象100%存活的极端情况。

在这种情况下,有人提出标记-整理算法,这种算法也是分为两个阶段:标记阶段和整理阶段。标记阶段我们不再详细解释,所谓的整理阶段,就是在标记完成之后,让所有存活的对象在内存中向一边移动,将死亡对象挤出内存。

4、分代收集算法

分代收集算法就是标记-清除算法、标记-整理算法和复制算法的结合,基本思想是:
根据对象存活周期将Java堆内存分为年轻代和老年代,年轻代使用复制算法,将年轻代分为一个Eden区和两个Survivor区(S0和S1区),内存占比大体为8:1:1。

每次只使用Eden区和一个Survivor区(如S0区),在需要回收时,就将存活的对象全部移动到另外一个Survivor区(S1),然后清除掉Eden区和第一个Survivor区(S0),下一次就使用Eden区和S1,将第二次GC的幸存者移到S0,清空Eden区和S1,如此反复。

每次GC幸存下来的对象年龄+1,当年龄达到默认阈值15时,移动到老年代。

之所以在年轻代中选择使用复制算法,是因为在年轻代中的对象大多是朝生夕死的对象,存活率比较低,所以可以付出少量的内存作为代价进行回收。但是老年代中对象的存活率相对于年轻代较高,也不存在额外的空间进行内存担保,所以就使用标记-清除算法或者标记-整理算法进行收集。

四、对象内存分配

在这里插入图片描述

JAVA中的垃圾回收分为Minor GC以及Full GC
Minor GC 意为年轻代GC,由于年轻代对象大都为朝生夕死的对象,所以Minor GC发生频率比较频繁,速度也比较快。
Full GC 为老年代GC,但是当出现Full GC时,一般都会伴随着至少一次的Minor GC,不过这种情况并非绝对,主要还是和垃圾收集器的收集策略有关。

1、对象优先分配在Eden

大多数情况下,对象的内存分配会发生在Eden区中,当Eden区没有足够的内存时,将会触发一次Minor GC。

2、大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串及数组。大对象对虚拟机的内存分配来说就是一个坏消息(比遇到一个大对象更加坏的消息就是遇到一群“朝生夕死”的“短命大对象”,写程序的时候应当避免),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。

3、长期存活的对象进入老年代

由于虚拟机会采用分代回收机制,所以就有必要识别哪些对象需要放在年轻代,哪些对象需要放在老年代中,在虚拟机中会为对象定义一个年龄计数器,如果对象在Eden经历过一次垃圾回收后还存活,移动至Survivor中,则对象年年龄加1,之后每经历一次垃圾回收,如果对象还存活,则将对象年龄加1,当对象年龄达到设置阈值时,将被移动到老年代,默认阈值为15,可以通过-XX:MaxTenuringThreshold来进行设置。

4、空间分配担保

在发生Minor GC之前,虚拟机会检查老年代中的最大的可用连续内存空间是否大于年轻代中所有存活对象的总空间,如果这个条件成立,则Minor GC是安全的,因为老年代可以满足所有年轻代存活对象进入老年代的极端情况。

如果不满足,虚拟机则会查看HandlePromotionFailure设置是否允许担保,如果允许,则检查老年代中的最大的可用连续内存空间是否大于之前每次晋升老年代对象的平均大小,如果满足就会触发一次Minor GC,如果不满足则会触发一次Full GC。

但是这种策略在JDK6之后有了改变:老年代中的最大的可用连续内存空间如果大于之前每次晋升老年代对象的平均大小或者新生代中存活对象的总大小,则触发一次Minor GC,否则进行Full GC。

其实归根结底,JAVA中的内存分配和垃圾回收策略都是为了使内存得到更高效的利用,并且希望将更多的内存管理交由机器来处理从而解放程序员劳动力,使程序员可以从复杂的内存管理中抽身并减少人为管理内存中出现的错误。

五、垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

1、年轻代垃圾收集器

1)Serial 收集器

Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器。

特点:

  1. 年轻代收集器
  2. 单线程收集器
  3. 复制算法
  4. “Stop The Word”
  5. 一般Serial 与老年代 Serial Old 组合使用

2)ParNew

ParNew垃圾收集器是Serial收集器的多线程版本, 除了多线程外,其余的行为、特点和Serial收集器一样。

特点:

  1. 年轻代收集器
  2. 多线程收集器
  3. 复制算法
  4. 一般 ParNew 与老年代 CMS 组合使用

3)Parallel Scavenge收集器

Parallel Scavenge收集器又叫PS收集器,关注于吞吐量的收集器。
特点:

  1. 年轻代收集器
  2. 多线程收集器
  3. 复制算法
  4. 目标则是达一个可控制的吞吐量
  5. 一般 Parallel Scavenge 与老年代 Parallel Old 组合使用

2、老年代收集器

1)Serial Old 收集器

Serial Old是Serial 的老年代版本,同样是一个单线程收集器,使用“标记-整理算法”。
特点:

  1. 老年代收集器
  2. 单线程收集器
  3. 标记整理算法
  4. “Stop The Word”

2)CMS收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。使用“标记-清除算法”。

运作过程:
1、初始标记:标记GCRoot能直接关联的对象,“Stop The Word”。
2、并发标记:标记所有的Old对象
3、重新标记:修正 并发标记期间因用户程序继续运作而没有标记到的那部分对象,“Stop The Word”。
4、并发清除:使用标记-清除算法。

3)Parallel Old 收集器

Parallel Old 是 Parallel Scavenge的老年代版本,使用多线程和“标记-整理算法”。特点跟年轻代的差不多。

3、整堆收集器

G1 收集器

//TODO

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值