gc各种算法
① GC垃圾回收(3)- 三色标记算法
CMS过程在上篇文章 GC垃圾回收(2) 中已经写过。
它分为四个阶段:
其中 并发标记 阶段会有漏标的问题,为解决这个问题,采用了 "三色标记算法"
G1 GC(Garbage First Garbage Collector)是一种服务端应用使用的垃圾收集器,目标是用在 多核、大内存 的机器上,它在大多数情况下可以实现指定的GC暂停时间,同时还能保持较高的吞吐量。它的吞吐量相较PS+PO降低了大概10%~15%,但是大大降低了响应时间,大概200ms的程度
G1内存模型如下:
G1相较之前其它的垃圾回收器,对模型进行了改变,不再进行物理分代,采用逻辑分代。
它不再将连续内存分为Eden区和Old区,而是将内存分为一个个的Region。一块Region(分区)在逻辑上依然分代,分为四种:Eden,Old,Survivor,Humongous(大对象,跨多个连续的Region)。
它的每个分区都可能是年轻代也可能是老年代,但是在同一时刻只能属于某个代。
年轻代、幸存区、老年代这些概念还存在,成为了逻辑上的概念,这样方便复用之前分代框架的逻辑。在物理上不需要连续,这带来了额外的好处——有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象特别多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是G1名字的由来,即首先回收垃圾最多的分区。
新生代其实并不适用于这种算法,依然是在新生代满了的时候,对整个新生代进行回收——整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小。
G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩。每个分区的大小从1M到32M不等,但都是2的幂次方。
特点:
G1与CMS在并发收集时的算法没太大区别,用的是 三色标记 算法。但ZGC和Shenandoah使用的是 颜色指针 Colored Pointers。
主要用于分代模型中帮助垃圾回收。
为什么需要 card table ?
寻找存活对象并不是一件容易的事。从一个GC root对象寻找,可能被Old区对象引用,这个Old区对象又被Eden区对象引用,那么判断Eden区对象是否存活就需要遍历整个Old区存活对象看是否被Old区对象引用。这样的话每进行一次YGC就要扫描整个Old区。
所以JVM内部,将内存区域分为一个个的card,对象存在一个个的card里。当老年代某个card中的对象指向了年轻代,就会将这个card标记为 Dirty 。这么多card具体哪个是 Dirty的,用位图BitMap来代表(如0110010010,1表示Dirty),这就是Card Table。
Card Table :由于做YGC时,需要扫描整个Old区,效率非常低,所以JVM设计了Card Table, 如果一个Old区Card Table中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card。 在结构上,Card Table用BitMap来实现。
RSet会占用一定的空间,所以ZGC又做了改进,不使用RSet,用颜色指针来标记。
Rset与赋值的效率:
5% ~ 60%(新生代)
G1能跟踪STW停顿时间,根据停顿时间动态调整新生代(Y区)比例
超过单个region的 50% 就是一个大对象,也可跨越多个region。
注意: G1也是存在FGC的,并且一定会被触发。当对象分配不下是会产生FGC。
回收时不分新生代还是老年代什么的,region满了就回收。
MixedGC过程:
跟CMS非常像,MixedGC最后是筛选回收,多了个筛选步骤。筛选就是找出垃圾最多的region。筛选后将存活对象复制到其他region,再将之前的region清空。
CMS和G1在并发标记时使用的是同一个算法: 三色标记法 ,使用白灰黑三种颜色标记对象。白色是未标记;灰色自身被标记,引用的对象未标记;黑色自身与引用对象都已标记。
在remark过程中,黑色指向了白色,如果不对黑色重新扫描,则会漏标。会把白色D对象当作没有新引用指向从而回收掉。
并发标记过程中,Mutator删除了所有从灰色到白色的引用,会产生漏标。此时白色对象应该被回收
产生漏标问题的条件有两个:
1.黑色对象指向了白色对象
2.灰色对象指向白色对象的引用消失
所以要解决漏标问题,打破两个条件之一即可:
为什么G1采用SATB而不用incremental update?
因为采用incremental update把黑色重新标记为灰色后,之前扫描过的还要再扫描一遍,效率太低。
G1有RSet与SATB相配合。Card Table里记录了RSet,RSet里记录了其他对象指向自己的引用,这样就不需要再扫描其他区域,只要扫描RSet就可以了。
也就是说 灰色-->白色 引用消失时,如果没有 黑色-->白色,引用会被push到堆栈,下次扫描时拿到这个引用,由于有RSet的存在,不需要扫描整个堆去查找指向白色的引用,效率比较高。SATB配合RSet浑然天成。
② GC是如何是如何启动及GC中的算法等
首先,我们先看看GC处理的内存区域在hotSpot(jdk1.8用的这个虚拟机)中是如何划分的.
虚拟机将内存划分为两大区域,新生代与老年代.
而在新生代中
虚拟机又将区域划分为Eden和两块survivor,新创建的对象将会在占有较大区域的Eden和一块survivor,当GC处理垃圾是首先进行标记,会将剩余的存活对象复制之后放在另外survivor中,然后进行清除,清除之会.
那么这里就有一个问题,如果存活对象过多 survivor放不下怎么办?
这里就会使用担保:将溢出的对象放入老年代之中.
如果是老年代那,因为老年代的对象的存活能力很强,且无法有空间为老年代进行担保,所以老年代使用的事 标记-整理
算法进行垃圾回收的,当GC标记清除了可回收的对象,会将剩余对象向一端移动.
那么GC是如何一步步的进行垃圾回收的那?
首先GC要启动可达性算法,那么GC是如何快速的找出所有的GCROOTS节点的那?
1. hotSpot是使用OopMap这样一组数据结构进行记录的,类加载完成之后
会记录对象内什么偏移量是什么类型(书上的话),编译器也会记录栈和寄存机中的位置.,而这个数据会在特点的位置进行记录,这些位置就叫做安全点(safepoint).
还有 GC的启动需要暂时挂起所有的线程,那么GC是在什么时间进行垃圾回收的那?
1.当线程运行到safepoint的时候才会进行GC,那么GC开始之后需要挂起所有线程,这是GC会选择使用主动式的抢断,也就是说GC会设置一个和安全点重合的轮询点,让所有线程都去访问这个轮询点,如果线程访问结果为真,那么就代表线程到了轮询点,便会记性线程中断了.
③ java常见gc算法有哪些
1:标记—清除 Mark-Sweep
过程:标记可回收对象,进行清除
缺点:标记和清除效率低,清除后会产生内存碎片
2:复制算法
过程:将内存划分为相等的两块,将存活的对象复制到另一块内存,把已经使用的内存清理掉
缺点:使用的内存变为了原来的一半
进化:将一块内存按8:1的比例分为一块Eden区(80%)和两块Survivor区(10%)
每次使用Eden和一块Survivor,回收时,将存活的对象一次性复制到另一块Survivor上,如果另一块Survivor空间不足,则使用分配担保机制存入老年代
3:标记—整理 Mark—Compact
过程:所有存活的对象向一端移动,然后清除掉边界以外的内存
4:分代收集算法
过程:将堆分为新生代和老年代,根据区域特点选用不同的收集算法,如果新生代朝生夕死,则采用复制算法,老年代采用标记清除,或标记整理
面试的话说出来这四种足够了
④ jdk8中,GC用到的的算法有哪些
主要应用Mark-sweepalgorithm(标记消除算法)即从根object(程序直接访问的)开始标记可到达的object算法基于有向图,采用深度优先搜索最后推荐你一本书,《java程序设计语言》([美]KenArnold,JamesGosling,DavidHolmes)上面有对
⑤ 如何设置java gc回收算法
在java和c#语言中,使用的是托管代码,不像c++语言那样由程序员进行内存的手动分配和回收,java语言则由JVM即Java虚拟机 全权负责堆内存的管理,这样子大大减少了程序员的负担,同时一定程度上提高了开发效率和系统稳定性,而常用的GC垃圾回收算法有哪些呢?
Java的堆是一个运行时数据区,类的实例(对象)从中分配空间。Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,这些对象通过new、newarray、anewarray和multianewarray等指令建立,但是它们不需要程序代码来显式地释放。一般来说,堆的是由垃圾回收 来负责的,尽管JVM规范并不要求特殊的垃圾回收技术,甚至根本就不需要垃圾回收,但是由于内存的有限性,JVM在实现的时候都有一个由垃圾回收所管理的堆。垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。
⑥ 深入理解GC垃圾回收机制
在我们程序运行中会不断创建新的对象,这些对象会存储在内存中,如果没有一套机制来回收这些内存,那么被占用的内存会越来越多,可用内存会越来越少,直至内存被消耗完。于是就有了一套垃圾回收机制来做这件维持系统平衡的任务。
1.确保被引用对象的内存不被错误的回收
2.回收不再被引用的对象的内存空间
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时, 计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
优点:引用计数收集器可以很快地执行,交织在程序的运行之中。
缺点:很难处理循环引用,比如上图中相互引用的两个对象,计数器不为0,则无法释放,但是这样的对象存在是没有意义的,空占内存了。
引用计数法处理不了的相互引用的问题,那么就有了可达性分析来解决了这个问题。
从GC Roots作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之最终不能与GC Roots有引用关系的视为不可达,不可达对象即为垃圾回收对象。
我自己的理解是,皇室家族每过一段时间,会进行皇室成员排查,从皇室第一代开始往下找血缘关系的后代,如果你跟第一代皇室没有关系,那么你就会被剔除皇室家族。
1.虚拟机栈中引用的对象(正在运行的方法使用到的变量、参数等)
2.方法区中类静态属性引用的对象(static关键字声明的字段)
3.方法区中常量引用的对象,(也就是final关键字声明的字段)
4.本地方法栈中引用的对象(native方法)
1.显示地赋予某个对象为null,切断可达性
在main方法中创建objectA、objectB两个局部变量,而且相互引用。相互引用直接调System.gc()是回收不了的。而将两者都置为null,切断相互引用,切断了可达性,与GCRoots无引用,那么这两个对象就会被回收调。
2.将对象的引用指向另一个对象
这里将one的引用也指向了two引用指向的对象,那么one原本指向的对象就失去了GCRoots引用,这里就判断该对象可被回收。
3.局部对象的使用
当方法执行完,局部变量object对象会被判定为可回收对象。
4.只有软、弱、虚引用与之关联
new出来的对象被强引用了,就需要去掉强引用,改为弱引用。被弱引用之后,需要置空来干掉强引用,达到随时可回收的效果。
只被软引用的对象在内存不足的情况,可能会被GC回收掉。
只被弱引用持有的对象,随时都可能被GC回收,该对象就为可回收对象。
是不是被判定为了可回收对象,就一定会被回收了呢。其实Ojbect类中还有一个finalize方法。这个方法是对象在被GC回收之前会被触发的方法。
该方法翻译过来就是:当垃圾回收确定不再有对该对象的引用时,由垃圾回收器在对象上调用。子类重写finalize方法以处置系统资源或执行其他清除。说人话就是对象死前会给你一个回光返照,让你清醒一下,想干什么就干什么,甚至可以把自己救活。我们可以通过重写finalize方法,来让对象复活一下。
示例:
执行的结果:
这里我们重写FinalizeGC类的finalize方法, 使用FinalizeGC.instance = this语句,让对象又有了引用,不再被判定为可回收对象,这里就活了。然后再置空再回收一下,这个对象就死了,没有再被救活了。所以finalize方法只能被执行一次,没有再次被救活的机会。
在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。
元空间有注意有两个参数:
MetaspaceSize :初始化元空间大小,控制发生GC阈值
MaxMetaspaceSize : 限制元空间大小上限,防止异常占用过多物理内存
为什么移除永久代?
移除永久代原因:为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代。
有了元空间就不再会出现永久代OOM问题了!
1.Generational Collection(分代收集)算法
分代收集算法是GC垃圾回收算法的总纲领。现在主流的Java虚拟机的垃圾收集器都采用分代收集算法。Java 堆区基于分代的概念,分为新生代(Young Generation)和老年代(Tenured Generation),其中新生代再细分为Eden空间、From Survivor空间和To Survivor空间。 (Survivor:幸存者)
分代收集算法会结合不同的收集算法来处理不同的空间,因此在学习分代收集算法之前我们首先要了解Java堆区的空间划分。Java堆区的空间划分在Java虚拟机中,各种对象的生命周期会有着较大的差别。因此,应该对不同生命周期的对象采取不同的收集策略,根据生命周期长短将它们分别放到不同的区域,并在不同的区域采用不同的收集算法,这就是分代的概念。
当执行一次GC Collection时,Eden空间的存活对象会被复制到To Survivor空间,并且之前经过一次GC Collection并在From Survivor空间存活的仍年轻的对象也会复制到To Survivor空间。
对象进入到From和To区之后,对象的GC分代年龄ege的属性,每经过GC回收存活下来,ege就会+1,当ege达到15了,对象就会晋级到老年代。
2.Mark-Sweep(标记-清除)算法
标记清除:标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。标记-清除算法主要是运用在Eden区,该区对象很容易被回收掉,回收率很高。
3.Copying(复制)算法
复制算法的使用在From区和To区,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
缺点:可使用内存缩减为一半大小。
那么复制算法使可使用内存大小会减半,设计上是怎么解决这个问题的呢。就是给From和To区划分尽可能小的区域。经过大数据统计之后,对象在第一次使用过后,绝大多数都会被回收,所以能进入第一次复制算法的对象只占10%。那么设计上,Eden、From、To区的比例是8:1:1,绝大多数对象会分配到Eden区,这样就解决了复制算法缩减可用内存带来的问题。
4.Mark-Compact (标记—整理)算法
在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记—清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记—整理算法,与标记—清除算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使它们紧凑地排列在一起,然后对边界以外的内存进行回收,回收后,已用和未用的内存都各自一边。
垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现:
Serial 收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,
优点是简单高效;
Serial Old 收集器 (标记-整理算法): 老年代单线程收集器,Serial 收集器
的老年代版本;
ParNew 收集器 (复制算法): 新生代收并行集器,实际上是 Serial 收集器
的多线程版本,在多核 CPU 环境下有着比 Serial 更好的表现;
CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行
收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿
的特点,追求最短 GC 回收停顿时间。