简单介绍JAVA垃圾回收,和常用的垃圾回收器

什么是垃圾:

任何一种语言在使用的时候都会产生垃圾,所谓的垃圾就是我们在计算机中分配出去的内存,但是已经没有人再去使用这个内存了。只不过有一些语言是需要我们自己进行垃圾回收的,例如:C,C++(优点:效率更高,缺点:编码时更复杂)。还有一些语言会有专门的垃圾回收期帮助我们进行回收,例如:JAVA,Python(优点:编码高效,缺点:效率相对缓慢,不过在垃圾回收器的不断提高下,效率已经渐渐提高很多)。

如果判断是否是垃圾的:

1:引用计数算法(Python)

每当这个内存地址增加一个引用时,计数器增加一。每当一个引用消失掉,计数器减一。(无法解决循环引用问题)

2:可达性分析算法(JAVA)

任何一个内存地址是否和GCRoot 存在着引用链连接。(GCRoot ==》A ==》B ==》C)

可以作为GCRoot的对象:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。
  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。

  • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。

  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。

  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如

    NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

  • 所有被同步锁(synchronized关键字)持有的对象。

  • 反映Java虚拟机内部情况的JM XBean、JVM TI中注册的回调、本地代码缓存等。

垃圾回收的常用算法:(如何回收垃圾)

1:分代收集算法

一般都会将jvm 的内存堆分成两部分,针对这两部分的特性不同,采用不同的收集算法:

  • 新生代
    • 一般都是一些占用内存小,生存周期短的对象。针对这类对象采用复制算法。
  • 老年代
    • 一般都是一些内存占用很大,或者是一些生存周期非常长的对象。针对这类对象采用标记清除或者标记整理算法。

2:复制算法

   主要负责收集,新生代的对象。这类对象内存占用小,生存周期短,比较符合复制算法的特点。

  • 优点
    • 清理过后的内存,非常整齐。对于下一次的内存分配效率非常高。
    • 清理的时间所消耗的时间很短。
  • 缺点
    • 需要额外的占用一下空间。会造成部分空间的浪费。
    • 且需要其他空间为期担保,才能保证内存空间的安全。
  • 实现
    • 一般采用将新生代分为Eden,S0,S1三个区域。
    • 第一次将对象全部分配置Eden。等到YoungGc发生后。将Eden剩余存活的对象全部转移至S0(或者S1)。
    • 下一次的新产生的对象继续分配置Eden区。等到YoungGc发生时。将Eden和上一次存储存活对象的S0(或者S1)区域中任然存活的对象,复制到另一个S1(或者S0)区域当中。之后的步骤,和这次一样,一直重复一下。
    • 如果有的对象在第一次分配的时候就就占用内存非常大,就会直接进入为新生代担保的区域(老年代)。
    • 如果有对象尽力的很多次YoungGc之后仍然存活,也会被分配置老年代(不同的垃圾回收期次数不同,也可以手动指定)

3:标记清除算法

  • 缺点
    • 由于所要进行的步骤比较多,所以执行的效率比较低。
    • 清理过后的内存区域比较不规整,容易造成大量的内存碎片。
  • 实现
    • 人如其名,该算法的主要逻辑分为两步,第一步标记所有的需要进行回收的对象(或者是反过来,所有不需要回收的对象)
    • 第二步,对需要回收的对象进行清理。

4:标记整理算法

  • 优点
    • 清理过后的内存比较整理,对于下一次内存分配效率比较高
  • 缺点
    • 实现比较复杂,效率低。
  • 实现
    • 首先第一步还是需要标记所有不需要回收的对象。
    • 第二步将所有不需要回收的对象向一端对齐移动。
    • 第三步移动完毕后,将该区域之外的内存进行清空。

10种不同的垃圾回收器(不同的保洁公司使用不同的垃圾清理机制)

以上图片来之马士兵老师的”马士兵课堂“所使用的图片。

ParNew,Serial,Parallel Scavenge,CMS,Serial Old, Parallel Old。这几个算法都是分代收集的算法,针对不同的区域(新生代/老年代)使用不同的垃圾收集器。然后两个区域的收集器进行配合使用。ParNew,Serial,Parallel Scavenge针对新生代区域,

CMS,Serial Old Parallel Old针对老年代区域。(图中使用虚线连接的,证明两个垃圾收集器可以在一起进行配合使用,未连接的这不可以配合使用)

Serial

  • 是最老的一款垃圾回收期,也是最稳定的一款。
  • 和他的名字一样,序列化的收集器。他的主要作用是收集新生代,采用的是STW(需要停止所有用户的工作线程,下面还会提到这个名词)并且是单线程的垃圾回收期。
  • 主要的针对目标是一些单线程的机器,和一些内存占用小的桌面型应用程序(因为内存占用小,所以一般一次垃圾回收也就几十好眠就可以搞定)
  • 主要的工作流程如下图

图片引用来自,周志明老师的”深入理解java虚拟机“。(Serial/Serial Old收集器运行示意图)

Serial Old

  • 和上面介绍的Serial一样都是一款同时代的垃圾回收期。
  • 主要负责收集老年代的垃圾收集器。
  • 同时他也是CSM收集器使用的担保收集器(在CMS处理不了的时候,使用该收集器进行回收)
  • 主要的工作流程如上图

ParNew

  • 他和新生代的Serial没有太多的区别,主要就是从单线程工作,转化为了多线程工作。
  • 他可以和Serial Old 垃圾收集器协同工作如下图。
  • 他目前一般的使用场景就是和CMS协同使用

图片引用来自,周志明老师的”深入理解java虚拟机“。(ParNew/Serial Old收集器运行示意图)

Parallel Scavenge

  • 他同样的也是一款新生代的垃圾回收器。
  • 他的主要目标就是可以控制垃圾回收的吞吐量(所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值)
  • 不过他的这个目标其实并不是那么的好用,他是以牺牲了GC次数来换取的单次GC执行时间的。也就是所如果一切条件不变的情况一下,未修改过(-XX:MaxGCPauseMillis,-XX:GCTimeRatio)GC吞吐量时间的时候,可能一次GC需要100毫秒,平均60秒进行一次GC。将吞吐时间修改为50毫秒后,可能需要30毫秒就要进行一次GC。
  • -XX:MaxGCPauseMillis:单次GC所消耗的最大时间(毫秒)
  • -XX:GCTimeRatio:是一个大于0小于100的整数,也就是垃圾收集时间占总时间的 比率,相当于吞吐量的倒数。譬如把此参数设置为19,那允许的最大垃圾收集时间就占总时间的5% (即1/(1+19)),默认值为99,即允许最大1%(即1/(1+99))的垃圾收集时间。
  • 他和Parallel Old协同使用的运行示意图如下图

图片引用来自,周志明老师的”深入理解java虚拟机“。(Parallel Scavenge/Parallel Old收集器运行示意图)

Parallel Old

  • 他是一款老年代的垃圾回收期。
  • 他的主要目的就是为了配合Parallel Scavenge协同使用(运行示意图如上图)。
  • 他和Parallel Scanvenge的组合同样是HotSpot JDK1.8的默认垃圾收集器

CMS

这款垃圾收集器虽然在HotSpot JDK1.5就诞生了,但是一直不是很稳定,任然存在很多问题。这款垃圾收集器也是HotSpot JDK的垃圾回收上一款划时代的垃圾回收期。因为之前的垃圾收集器都是需要进行STW的,并且大部分时间都是不可控的。但是CMS做到了在一定期间内可以允许垃圾回收器与用户线程进行并发工作(并发标记,并发清除),并且大大的缩短了垃圾回收器的工作时间。他的工作流程如下:

  • 初始标记:
    • 这个操作仍然需要进行STW。不过幸运的是这个动作只需要标记那些与GCRoot 直接关联的对象,这些对象很少(相对于标记所有的老年代对象),所以他所需要停顿的时间也很短。
  • 并发标记:
    • 这个阶段可以允许用户线程和垃圾回收期并发工作。虽然不会暂停用户线程,但是会造成用户线程的吞吐量变低(程序变慢,不过相对应STW还是好很多了)
  • 重新标记:
    • 这个操作也需要STW,他需要对那些并发标记过程中,产生的那些不正确标记进行纠正。不过这个过程也是比较快的,因为大部分的对象在上一个步骤已经标记完了,这个过程只是再次确认。
  • 并发清除:
    • 这个阶段也是可以和用户线程并发进行的。会并发清除掉之前标记的那些垃圾,不过在这个期间内产生的垃圾是无法进行回收的(因为他还没有被标记)。这部分垃圾称之为浮动垃圾,浮动垃圾只能等待下一次GC 才能进行清除。
    • 因为他是CMS存在与用户线程并发工作的场景,所以他无法在老年代完全装满后进行GC,所以他会在达到一定百分比的时候就进行GC。不过这样就会引起如果在于用户线程并发执行的过程中,又产生了很多老年代的对象,老年代无法在承载这些对象内存的时候就会触发备选条件,从而使用Serial Old垃圾回收期进行一次彻底的垃圾收集。

因为CMS在存在的种种问题,他始终没有被HotSpot 作为某一版本JDK的默认垃圾回收期。

G1

G1的垃圾收集器有事Hotspot历史上一款里程碑意义的垃圾收集器,他放弃了之前对堆内存进行新生代和老年代的物理分区。而是基于Region的内存布局方式(逻辑分区上,还是采用了新生代和老年代,任何一个Region都有可能是新生代,也有可能是老年代)。G1的目的是创造可预测停顿时间的模型,他的这里目的也是在Region布局的基础上实现的,他以Region为单次回收的最小单位,从而保证了可以预测停顿时间。他的工作流程如下:

  • 初始标记
    • 仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(下面有解释) 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。

  • 并发标记
    • 从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以 后,还要重新处理SATB(下面有解释)记录下的在并发时有引用变动的对象。

  • 最终标记
    • 对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SATB记录。

  • 筛选回收
    • 负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。

G1部分的介绍来自周志明老师的深入理解java虚拟机

TAMS:G1为每一个Region设 计了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过 程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。

SATB:是在三色标记算法中,应对并发标记时产生的对象消失问题,所对应的两种解决的算法。SATB是其中一种(原始快照),另一种是Incremental Update。G1,ZGC,Shenandoah都是采用的SATB算法。

Incremental Update:是在三色标记算法中的另一种解决并发表时产生的对象消失的解决方案,(增量更新)CMS就是采用的这种算法。

ZGC

还不是很稳定,使用的比较少,暂时先不做介绍

Shenandoah

还不是很稳定,使用的比较少,暂时先不做介绍

Epsilon

JDK上对这个特性的描述是:开发一个处理内存分配但不实现任何实际内存回收机制的GC, 一旦可用堆内存用完,JVM就会退出。
如果有System.gc()调用,实际上什么也不会发生(这种场景下和-XX:+DisableExplicitGC效果一样), 因为没有内存回收,这个实现可能会警告用户尝试强制GC是徒劳。

 

一个对象从出生到死亡的过程(内存分配至被回收)

  1. 判断栈空间内是否有内存,如果有直接在栈内存分配(这里面需要进行逃逸分析,如果通过逃逸分析,发现对象没有进行逃逸,则可以考虑在栈内存进行分配。简单的说就是看看对象的作用域有没有超过该栈针。)如果不能在栈内存分配,则进入环节二(判断是否是大对象)
  2. 判断是否是大对象,如果是大对象,不能够在新生代进行分配则直接进入老年代。如果不是大对象则进入环节三(TLAB内是否有空间)
  3. 判断TLAB(也是Eden区的一部分空间,是JVM虚拟机对多线程的优化,每一个线程在Eden区内都会有一个只属于自己的内存空间,其他线程无法访问。)内是否有空间如果有在TLAB内分配,如果没有在Eden内进行分配。
  4. 然后进行一次YoungGc,如果清除掉,则结束。如果清除不掉则年龄加一进入环节五(判断年龄)
  5. 判断该对象经历的YoungGc次数,如果超过标准(不同的垃圾回收期的次数不同,也可以手动指定)则进入老年代。否则,判断上一次所使用的是S0还是S1,如果是S0则拷贝至S1,如果是S1则拷贝至S0。然后重复步骤四
  6. 经历一次FullGc后,将在老年代中,GCRoot不可达的对象进行回收。

以上图片来之马士兵老师的”马士兵课堂“所使用的图片。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值