java基础之垃圾回收机制

每种语言都有自己的垃圾回收方式,c和c++没有回收机制,依靠程序员自己动态的管理或者自己写一个回收机制,而python是一引用计数和分带收集来进行垃圾回收,等等。根据自身语言的特点,使用的垃圾回收机制都各有优缺,所以要想写出好的代码,了解这些语言底层的运行机制还是非常有必要的,方便在以后的问题中也能考虑到。

垃圾收集(Garbage Collection)和内存管理是jvm中的两个核心知识点,内存管理以后再梳理下,今天先谈谈垃圾收集或者回收,下面都用GC代替。


垃圾对象的查找:

在处理垃圾的时候首先的是要发现不再使用的对象,通常有两种常用的方法:

引用计数法:主要的过程为,每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1,当引用离开作用域或者被置为null时,引用计数减1,垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0的时候,就释放其占用的空间。

  • 优点:简单而且判断效率高。
  • 缺点:速度很慢,而且很难解决对象之间的相互循环引用的问题。

所以没有应用于任何一种java虚拟机实现中,但是这里还是简单的了解下


根搜索算法: java和c#都是采用这种方法来判定方法是否存活的,这种算法的基本思路是通过一系列名为:“GC Roots”的对象来作为起始点,从这些节点开始向下搜索的,搜索所走过的路径称之为引用链,当一个对象到GC Roots没有任何引用链相连接时,就证明此对象是不可用的。

实际上,在根搜索算法中,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行根搜索后发现没有与GC Roots相连接的引用链,那它会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。如果该对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue队列中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行finalize()方法。finalize()方法是对象逃脱死亡命运的最后一次机会(因为一个对象的finalize()方法最多只会被系统自动调用一次),稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果要在finalize()方法中成功拯救自己,只要在finalize()方法中让该对象重引用链上的任何一个对象建立关联即可。而如果对象这时还没有关联到任何链上的引用,那它就会被回收掉。


垃圾收集算法:

首先来看下GC的算法有哪些:

  • 标记清除算法

    它所依据的思路是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活的对象,就会给对象设一个标记,这个过程不会回收人和对象,只有全部标记工作完成的时候,清除动作才会开始。在清理的过程中,你没有标记的对象将被释放,不会发生任何复制动作。

主要有以下缺点:
- 标记和清除过程的效率都不高。
- 标记清除后会产生大量的内存碎片,空间碎片太多可能会导致当程序在以后的运行过程中需要分配较大的对象时无法找到足够的连续内存而不得不触发另一次垃圾收集动作。

这里写图片描述

  • 复制算法

复制算法是在标记算法的基础上改动的,把内存空间划分为两个空间,每次只是用其中一个区域,垃圾回收时,遍历当前的使用区域,把正在使用中的对象复制到另一个区域中,然后再已使用过的内存空间一次清理掉。
这个算法的优点如下:
每次只对一块内存回收,运行高效。
只需移动栈顶指针,按顺序分配内存即可,实现简单。
内存回收时不用考虑内存碎片的出现。

缺点就是软件中经常碰到的问题,拿空间去换时间,所以这个算法需要两倍的内存去管理。

这里写图片描述

  • 标记整理算法

    复制算法比较适合于新生代,在老年代中,对象存活率比较高,如果执行较多的复制操作,效率将会变低,所以老年代一般会选用其他算法,标记阶段和标记算法一样,但是后续并不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后清理,因为要移动对象,所以它的效率比标记清理算法效率低,但是不会产生内存碎片。

这里写图片描述

分代收集:

当前商业虚拟机的垃圾收集都是采用的分代收集,它根据对象的存货周期的不同将内存划分为几块,一般是把java堆分为新生代和老年代,在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可以选用复制法来完成收集,而老年代中因为对象存活率高、没有额外的空间对它分配担保,就必须使用标记清除或者是标记整理算法来进行回收。


垃圾回收器的类型:

ParNew收集器:新生代收集器,使用复制算法,Serial收集器的多线程版,用多个线程进行GC,并行,其它工作线程暂停。使用-XX:+UseParNewGC开关来控制使用ParNew+SerialOld收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。

CMS(Concurrent MarkSweep)收集器:老年代收集器,致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial
Old进行内存回收,优先使用ParNew+CMS(原因见Full GC和并发垃圾回收一节),当用户线程内存不足时,采用备用方案Serial
Old收集。‘’

垃圾回收器的种类很多,互联网很多采用的是ParNew+CMS的垃圾收集器组合进行分代垃圾回收。就上面的两种,从JDK1.7之后使用的是新的垃圾收集器G1。这也是下面要详细说的,在其他的博客中了解到为什么要使用一种新的垃圾回收器,首先来了解下CMS的实现原理。

CMS对于堆内存的分代管理机制如下图所示:

这里写图片描述

它的运行示意图如下:

这里写图片描述

CMS全称Concurrent mark Sweep,所以很容易理解其采用的是标记清除的垃圾回收算法,从图中也可以看到整个垃圾回收过程分为五个阶段:初始标记、并发标记、重新标记、并发清理、重置线程。尽管并发标记、并发清理和重置线程已经可以和用户线程同时运行,但是初始标记阶段和重新标记两个阶段是需要停用户线程的。通常,初始标记阶段很快,重新标记阶段相对耗时。对于堆空间很大的场景,这个停服务时间可能就会长到无法接受。

另外一方面,CMS采用的是标记清除算法,这会导致内存碎片的产生,当需要为大对象分配空间时,就会出现找不到连续大空间的情况,这种情况下会触发一次Full GC,经过参数配置,Full GC在垃圾回收之前可以先进行一次内存合并整理,但是合并整理过程是无法需要Stop The World,所以解决了内存碎片问题的同时,又导致额外的停服务时间。

从上面可以看出CMS 的问题:

  • 垃圾回收过程会停止用户服务
  • 采用的是标记清除算法,会产生大量的碎片

G1的工作机制:

G1是一款替换CMS为目标并且面向服务端应用的垃圾收集器,它的本质思想是化整为零。如果每次垃圾回收可以选择堆空间的一部分进行回收,那么停顿赶时间可以被控制在合理的范围内。

化整为零:下面是G1中对内容的布局

这里写图片描述

在G1中依然是分代回收的概念,但是每一代的空间不再是简单的连续分配,他们被分散成多个大小相等存储块,每个块都可以进行单独的垃圾回收。它们被称作Region,因为可以单独进行垃圾回收,那就有能力将一次很长的GC 分解成多个短GC ,通过调整每一次需要回收的Region数量来控制停顿时间的长短。

那么在选在需要回收的Region上G1采用的是回收价值优先策略,能回收的空间越大,所需要的时间越短,就会获得较高的优先级而先被回收。

在做化整为零这件事上,G1和其他的搜集器一样都采用了Remembered Set(简称Rset)来避免全堆扫描,本质上就是一种空间换时间的算法,逻辑上每个Region都有一个Rset,它能够维护 谁引用了我的对象这样的信息,所以当对Region内的对象进行可达性分析的时候结合Rset的信息就可以避免对全堆进行扫描。


停顿测试模型:

因为做到了化整为零,所以G1可以建立停顿预测模型,通过参数-XX:MaxGCPauseMillis指定一个收集过程的目标停顿时间,它会对每个Region垃圾回收耗时的历史数据进行分析,建立停顿预测模型,来预测当前时间点对每个Region进行垃圾回收可能消耗的时间,再根据用户设定的目标停顿时间,就可以计算出来需要回收的Region数量。

需要注意的是,因为依赖于根据历史数据预测垃圾回收时间,因此并不能保证百分百的准确性,所以这里的目标停顿时间是一个期望值,并不是硬性条件。


标记整理:
G1 的另一个改进在于采用标记整理避免内存碎片,下面是G1垃圾回收各个阶段的示意图:

这里写图片描述

从上面的图中可以看到,与CMS相比,整个垃圾回收的过程没有太多的变化,最重要的是采用了标记整理代替了标记清除算法,解决了内存碎片的问题。

总结下来G1的有点有:
1、将垃圾回收化整为零,减少对用户服务的影响
2、垃圾回收时间可配置
3、避免内存碎片


虽然在编程的时候不需要考虑这些垃圾的回收,但是好的Java程序在编写的时候肯定要考虑GC的问题,怎样定义static对象,怎样new对象效率更高等等问题才能编写出高效的程序。要想把GC使用好还要在去了解下jvm中的内存管理的知识。以上就是关于Java中GC的一些内容。


Reference:

《java编程思想》

《java虚拟机规范》

深入理解 Java G1 垃圾收集器

从Java6到Java8-你应该知道的JVM新特性

[深入Java虚拟机(8)]:Java垃圾收集机制

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值