1.概述

垃圾回收器的历史比Java久远,诞生于Lisp(第一门开始使用内存动态分配和垃圾收集技术的语言).

2.对象已死?

1.引用计数法

  • 概述:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能在被使用的.
  • 但主流Java虚拟机并没有采用:当单纯的引用计数就很难解决对象之间的相互循环引用的问题.

2.可达性分析算法

  • 概述:通过一系列称为"GC Roots"的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为"引用链",如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能在被使用的.
  • 固定可作为GC Roots的对象包括以下几种:
  1. 在虚拟机中引用的对象.(各个线程被调用的方法堆栈中使用到的参数,局部变量,临时变量等).
  2. 在方法区中静态属性引用的对象(引用类型静态变量).
  3. 在方法区中常量引用的对象(字符串常量池里的引用).
  4. 在本地方法栈中JNI引用的对象
  5. java虚拟机内部的引用(基本数据类型对应的Class对象,一些常驻的异常对象,系统类加载器).
  6. 所有被同步锁(synchronized 关键字)持有的对象.
  7. 反应Java虚拟机内部情况的JMXBean,JVMTI中注册的回调,本地代码缓存等.
  8. 其他"临时性"的对象

3.引用

  • 概述:JDK 1.2之前,reference类型数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某块内存,某个对象的引用.
    JDK 1.2之后分为,强引用,软引用,弱引用,虚引用

  • 强引用:普遍存在的引用赋值.垃圾收集器永远不会回收被引用的对象.

  • 软引用:用来描述一些还有用,但非必须的对象.在系统将要发生内存溢出异常前,会把这些对象列进回收的范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常.

  • 弱引用:用来描述那些非必要对象.被弱引用引用的对象只能生存到下一次垃圾收集发生为止.当垃圾回收开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象.WeakReference类来实现弱引用.

  • 虚引用(“幽灵引用”,“幻影引用”):唯一目的只是为了能够在这个对象被收集器回收时收到一个系统通知.PhantormReference类来实现虚引用.

4.生存还是死亡?

  • 概述:在可达性算法中真正宣告一个对象死亡的不是判定为不可达对象,而是要经过两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那他将会被第一次标记,随后进行第一次筛选,筛选的条件是此对象是否有必要执行finalize()方法.假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为:“没必要执行”.如果对象被判定为确有必要执行finalize()方法,那么该对象将会被放置在一个名为F-Queue的队列中,并在稍后由一条有虚拟机自动建立的.地调度优先级的Finalizer线程去执行它们的finalize()方法.
  • finalize()方法是对象逃脱死亡命运的最后机会—只要重新与引用链上任何一个对象建立关联即可.
  • 任何一个对象的finalize()方法,系统只会自动调用一次.

5.回收方法区

  • <<java虚拟机规范>>中可以不要求虚拟机在方法区中实现垃圾收集.(例:JDK 11的ZGC收集器就不支持类卸载),因为方法区垃圾回收的"性价比"比较低,而在新生代进行一次垃圾回收通常可以回收70%到80%的内存空间.
  • 方法区垃圾回收的主要两部分内容:废弃的常量不再使用的类型.
  1. 废弃的常量:未被任何对象引用的常量.
  2. 不再使用的类型(满足三个条件后被允许回收,不是必然回收):1.该类所有的实例都已经被回收.2.该类的加载器已经被回收.3.该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法.

3.垃圾收集算法

从如何判定对象消亡的角度出发,垃圾收集算法可以划分为"引用计数式垃圾收集"和"追踪式垃圾收集"两大类.也被称作"直接垃圾收集"和"简介垃圾收集".

1.分代收集理论

  • 弱分代假说:绝大多数的对象都是朝生夕灭的.
  • 强分代假说:熬过越多次垃圾收集过程的对象就难以消亡.

以上两点奠定了垃圾回收器的分代的设计原则.

  • 跨代引用假说:跨代引用相对于同代引用来说仅占少数.

这一个假说是为了解决不同代之间的引用,而导致除了收集到该区域的存活对象,还要额外遍历其他代的所有对象是否可达性分析的正确性的问题.

-解决该问题只需要在新生代上建立一个全局的数据结构(记忆集),这个结构把老年代划分成若干小块,表示出老年代的那一块内存会存在跨代引用.在发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描.

2.标记-清除算法

  • 概述:最早出现也是最基础的算法,首先要标记出所有需要回收的对象,标记完成之后,统一回收所有被标记的对象.
  • 缺点:第一个:执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这是必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;第二个:内存空间的碎片化问题.清楚后会产生大量的不连续的内存碎片,再分配大的对象时找不到一块连续的内存空间,而不得不触发一次垃圾回收动作.
  • 收集器代表:CMS收集器

4.HotSpot的算法细节实现

1.根节点枚举

  • 根节点枚举这一步骤都是要暂停用户线程的,“Stop The World”.
  • 现在可达性分析算法耗时最长的查找引用链的过程已经可以做到与用户线程一起并发,但根节点枚举始终还是必须在一个能保障一致性的快照中才得以进行
  • 由于目前主流的Java虚拟机使用的是准确式垃圾收集,所以当用户线程停顿下来之后,其实并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得到那些地方存放着对象引用的.在HotSpot的解决方案里,是使用一组称为OopMap的数据结构来达到这个目的.一旦类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用.这样就不需用一个不漏的从方法区等GC Roots开始查找.

2.安全点

  • HotSpot没有为每条指令都生成OopMap,只是在"特定的位置"记录了这些信息,这些位置被称为安全点.有了安全点的设定,也就决定了用户程序执行时并非在代码指令流的任意位置都能够停顿下来开始垃圾收集,而是强制要求必须执行到达安全点后才能够暂停.
  • 安全点的选取:“是否具有让程序长时间执行的特征”.(例如方法调用,循环跳转,异常跳转等指令序列的复用.)
  • 如何确保垃圾收集发生是让所有的线程都跑到最近的安全点上,然后停顿下来?
    方案一:抢先式中断:不需要线程的执行代码注定去配合,在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上.(现在几乎没有使用的)
    方案二:主动式中断:当垃圾收集器需要中断线程的时候,不直接对线程操作,仅仅简单的设置一个标志位,各个线程执行的过程时会不停的主动去轮询这个标志,一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起.轮询标志的地方和安全点是重合的,另外还要加上所有创建对象和其他需要在Java堆上分配内存的地方,这是为了检查是否即将要发生垃圾收集器,避免没有足够内存分配新对象.
  • HotSpot使用内存保护陷阱的方式,把轮询操作精简至只有一条汇编指令的程度.当需要暂停用户线程时,虚拟机把内存页设置为不可读,那线程执行到对应的指令时就会产生一个自陷异常信号,然后在预先注册的异常处理器中挂起线程实现等待,这样仅通过一条汇编指令便完成安全点轮询和出发线程中断了.

3.安全区域

  • 概述:安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的.
  • 存在的原因:安全的的机制保证了程序执行时,在不长时间内就会遇到可进入垃圾收集过程的安全点.但在程序"不执行"的时候,就是没有被分配处理器的时间,如Sleep状态或者Blocked状态,这时候线程无法响应虚拟机的中断请求,不能再走到安全的地方去挂断自己,虚拟机也显然不可能持续等待线程重新被激活分配处理器时间.
  • 原理:当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,那这样当这段时间里虚拟机要发起垃圾收集器时就不必去管这些已声明自己在安全区域内的线程了.当线程要离开安全区域时,他要检查虚拟机是否已经完成了根节点枚举(或者垃圾收集过程中其他需要暂停用户线程的阶段),如果完成了,那线程就当作没事发生过,继续执行;否则它就必须一直等待,直到收到可以离开安全区域的信号为止.

4.记忆集和卡表

  • 由来:为了解决跨代问题的垃圾回收,需要建立记忆集的数据结构,避免进行GC Roots全局扫描.
  • 概述:记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构.
  • 记录粒度:
    字长精度:每个记录精确到一个机器字长,该字包含跨代指针.
    对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针.
    卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针.
  • 其中卡精度使用一种称为"卡表"的方式去实现记忆集.
  • 卡表最简单的形式可以只是一个字节数组,而HotSpot虚拟机确实也是这样做的.
CARD_TABLE [this address >> 9] = 0;

字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作"卡页";卡页的大小都是以2的N次幂的字节数.

-一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏,没有则标识为0.在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡表内存块中包含跨代指针,把他们加入GC Roots中一并扫描.

5.写屏障

  • 由来:为了解决卡表元素如何维护的问题(如:何时变脏,谁来把它们变脏等).
  1. 何时变脏?有其他分代区域中对象引用了本区域对象时,其对应的卡表元素就应该变脏,变脏时间点原则上应该发生在引用类型字段赋值的那一刻.
  2. 如何变脏?针对于解释执行的字节码,虚拟机负责每条字节码指令的执行,有充分的介入空间;针对于编译执行的场景中,即时编译过后的代码已经是机器指令流了,只能从机器码层面入手,把维护卡表的动作放在每一个赋值操作之中.
  3. HotSpot虚拟机里是通过写屏障技术来维护卡表状态的.写屏障可以看作在虚拟机层面对"引用类型字段赋值"这个动作的AOP切面,在引用对象赋值时会产生一个闭环通知,供程序执行额外的动作,也就是所赋值的前后都在写屏障的覆盖范畴内.在赋值前的部分的写屏障叫做写前屏障,在赋值后的则叫做写后屏障.HotSpot的许多收集器都用到了写屏障,但G1收集器出现后只用到了写后屏障.
  • 除了写屏障后,虚拟机就会为所有赋值操作生成相应的指令,一旦收集器在写屏障中增加了更新卡表操作,无论更新的是不是老年代对新生代对象的引用,每次只要对引用进行更新,就会产生额外的开销,不过这个开销与Minor GC时扫描整个老年代的代价相比还是低的很多.
  • 除了写屏障的开销外,卡表在高并发场景下还面临着"伪共享"问题.现代中央处理器的缓存系统中是以缓存行为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰好共享同一个缓存行,就会彼此影响(写回,无效化或者同步)而导致性能降低 ,这就是伪共享问题.
  • 为了解决此问题:不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表未被标记过时才将其标记为变脏,即将卡表更新的逻辑变为以下代码所示
 if(CARD_TABLE[this address>>9]!=0)CARD_TABLE[this address>>9]=0;

JDK 7 之后HotSpot虚拟机增加了一个新的参数 -XX:+UseCondCardMark 开启卡表更新的条件判断.

6.并发的可达性分析

  • 概述:可达性算法理论上要求全过程都基于一个能保障一致性的快照才能进行分析,这意味着必须全程冻结用户线程的运行.
  • 并发扫描时对象消失的问题解决方案:增量更新原始快照.
  • 应用实现:CMS是基于增量更新来做并发标记的,G1,Shenandoah则是用原始快照来实现.

5.经典垃圾收集器

1.Serial收集器

  • 单线程
  • 新生代
  • 进行垃圾收集时,“Stop The World”
  • 算法:复制算法
  • 适用:HotSpot虚拟机在客户端模式下的默认新生代收集器.
  • 优点:简单而又高效.对于内存资源受限的环境,它是所有收集器里额外内存消耗最小;对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率.

2.ParNew收集器

  • 多线程
  • 新生代
  • 算法:复制算法
  • Stop The World
  • 与CMS配合使用
  • 适用:多CPU环境的服务端

3.Parallel Scavenge 收集器

  • 多线程
  • 新生代
  • 算法:复制算法
  • 特点:Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量.
  • 吞吐量=运行用户代码时间/(运行用户代码时间+运行垃圾收集时间)
  • 吞吐量参数控制:
    控制最大垃圾收集停顿时间:-XX:MaxGCPauseMillis;
    控制吞吐量大小:-XX:GCTimeRation;
  • 垃圾自适应调节策略:-XX:UseAdaptiveSizePolicy.

4.Serial Old收集器

  • 单线程
  • 老年代
  • Stop The World
  • 算法:标记整理
  • 适用:供客户端模式下的HotSpot虚拟机使用.
  • 两种用途:一:JDK5以及之前的版本中与Parallel Scavenge收集器搭配使用,二:作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用.

5.Parallel Old收集器

  • 多线程
  • 老年代
  • 算法:标记-整理
  • 吞吐量优先

6.CMS收集器

  • 并发
  • 算法:标记-清除
  • 适用:互联网网站或者基于浏览器的B/S系统的服务端,关注响应速度,带来良好交互体验.
  • 运行过程:
    1.初始标记:“Stop The World”,仅仅只是标记一下GC Roots能直接关联到的对象,速度很快.
    2.并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程,耗时较长,但不需要停顿用户线程,可以与垃圾收集线程一起并发运行;
    3.重新标记:“Stop The World”,为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,停顿时间比初始标记阶段稍长,但远比并发标记阶段的时间短;
    4.并发清除:清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的.
  • 优点:并发收集,低停顿.
  • 缺点:CMS收集器对处理器资源非常敏感;会产生"浮动垃圾",交给下一次垃圾收集,如果CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次"并发失败",启用后备方案:冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集器,停顿时间加长;会产生大量的碎片,有可能触发Full GC,CMS收集器提供了-XX:CMSFullGCsBeforeCompaction参数:要求CMS收集器在执行过若干次不整理空间的Full GC之后,下一次进入Full GC前会先进行碎片整理…

7.Garbage First收集器

  • 概述:G1收集器开创了收集器面向局部收集的设计思路和基于Region的内存布局形式,“全功能的垃圾收集器”.

  • 适用:面向服务器应用的垃圾收集器.

  • 算法:从整体看标记-整理;从局部看(两个Region之间)标记-复制.

  • 缺点:相比于CMS产生的垃圾占用(卡表实现更为复杂),额外的执行负载(CMS使用写后屏障,G1使用写前屏障维护卡表,写后屏障实现原始快照搜索(SATB)算法)都高,.

  • 面向堆内存任何部分来组成回收集,主要回收依据以回收效率这就是G1收集器的Mixed GC模式.

  • G1开创基于Region的内存布局是它能够实现这个目标的关键.设计的理论是依靠分代收集,实际堆内布局是:把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要扮演新生代的Eden空间,Survivor空间,或者老年代空间.

  • Region中还有一类特殊的HUmongous区域,专门用来存储大对象.G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象,会被存放在N个连续的Humongousn Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分进行看待.

  • G1之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,每次回收内存空间都是Region大小的整数倍,这样可以有计划的避免在整个Java堆中进行全区域的垃圾收集.

  • 具体的处理思路:让G1收集器去跟踪各个Region里面的垃圾堆积的"价值"大小,价值即回收所获得得空间大小以及回收所需要时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的手机停顿时间,优先处理回收价值收益最大的那些Region,在有限的时间内获取尽可能高的收集效率.

  • 问题:1.如何在并发标记阶段保证收集线程与用户线程互不干扰的运行?G1把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上.G1收集器默认在这个地址以上的对象是被隐式标记过的,即默认它们是存活的,不纳入回收范围.如果内存回收的速度赶不上内存分配的速度,G1收集器也要被迫冻结用户线程执行,导致Full GC而产生长时间"Stop The World".

  • 问题二:将Java堆分成多个独立Region后,Region里面的跨Region引用对象如何解决?使用记忆集(本质上是一种哈希表),Key是别的Region的起始地址,Value是一个集合,包含的是卡表的索引号,这种"双向"的卡表结构比较占内存.

  • 问题三:怎样建立起可靠的停顿预测模型?G1的停顿预测模型是以衰减均值为理论基础来实现的,在垃圾收集过程中,G1收集器会记录每个Region的回收耗时,每个Region记忆集里的脏卡数量等各个可测量的步骤花费成本,并分析得出平均值,标准偏差,置信度等统计信息.

  • 如果不计算用户线程运行过中的动作(如使用写屏障维护记忆集的操作),G1收集器的运作过程大致分为四个步骤:

  1. 初始标记:仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一个阶段用户线程并发运行时,能正确地在可用的Region中分配新对象.这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿.
  2. 并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,蛋壳与用户程序并发执行.当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象.
  3. 最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录.
  4. 筛选标记:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来指定回收计划,可以自由选择任意多个Region中,再清理掉整个旧Region的全部空间.因为牵扯到移动对象,必须要暂停用户线程,有多条收集器线程并行完成的.

8.总结(截至JDK 1.8)

6.低延迟垃圾收集器

1.Shenandoah收集器

  • 目标:实现一种能在任何堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的垃圾收集器,不仅要进行并发的垃圾标记,还要并发地进行对象清理后的整理动作.
  • 它类似于G1的继承者,绝大部分设计一样,但也至少有三处不同,
  1. 最重要的是支持并发的整理算法,G1的回收阶段是可以多线程并行的,但却不能与用户线程并发.
  2. Shenandoah是默认不使用分代收集的,换言之,不会有专门的新生代Region或者老年代Region的存在,没有实现分代,并不是说分代对Shenandoah没有价值,这更多是处于性价比的权衡,基于工作量上的考虑而将其放到优先级较低的位置上.
  3. Shenandoah摒弃了在G1中耗费大量内存和计算资源去维护的记忆集,改名为"连接矩阵"的全局数据结构(类似于一张二维表)来记录跨Region的引用关系,降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题的发生概率.
  • Shenandoah收集器的工作过程大致划分为以下九个阶段(在Shenandoah 2.0 中进一步强化了"部分收集"的特性,初始标记之前还存在Initial Partial,Concurrent Partial,Final Partial阶段,可以不太严谨的对应于以前的分代收集中的Minor GC的工作):

    1. 初始标记:首先标记与GC Roots直接关联的对象,这个阶段仍是"Stop The World"的,但停顿时间与堆大小无关,只与GC Roots的数量相关.
    2. 并发标记:遍历对象图,标记出全部可达的对象,这个阶段是与用户线程一起并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度.
    3. 最终标记:处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集.最终标记阶段也会有一小段短暂的停顿.
    4. 并发清理:这个阶段用于清理那些整个区域内连一个存活对象都没有找到的Region.
    5. 并发回收:Shenandoah要把回收集里面的存活对象先复制一份到其它未被使用的Region之中.复制对象这件事如果将用户线程冻结起来再做那是相当简单的.但如果两者必须要同时并发进行的话,就变得困难了.困难点在于移动对象的同时,用户线程任然可能不停对被移动的对象进行读写访问,移动对象是一次性的行为,但移动之后整个内存中所有指向该对象的引用都还是就对象的地址,这是很难一瞬间全部改变过来的.对于并发回收阶段遇到的这些困难,Shenandoah将会通过读屏障和被称为"Brooks Pointers"的转发指针来解决.并发回收阶段运行的时间长短取决于会收集的大小.
    6. 初始引用更新:并发回收阶段赋值对象结束后,还需要把堆中所有指向旧对象的引用修正到复制后的新地址,这个操作称为引用更新.引用对象更新的初始化阶段实际上并未做什么具体的处理,设立这个阶段只是为了建立一个线程集合点,确保所有并发回收阶段中进行的收集器线程都以完全分配给他们的对象移动任务而已.初始引用更新时间很短,会产生一个非常短暂的停顿.
    7. 并发引用更新:真正开始进行引用更新操作,这个阶段是与用户线程一起并发的,时间长短取决于内存中涉及的引用数量的多少.并发引用更新与并发标记不同,他不在需要沿着对象图来搜索,只需要按照内存物理地址的顺序,线性的搜索出引用类型,把旧值改为新值即可.
    8. 最终引用更新:解决了堆中的引用更新后,还要修正存在于GC Roots中的引用.这个阶段是Shenandoah的最后一次停顿,停顿时间只与GC Roots数量相关.
    9. 并发清理:经过并发回收和引用更新之后,整个回收集中所有的Region已再无存活对象,这些Region都变成Immediate Garbage Regions了,最后在调用一次并发清理过程来回收这些Region的内存空间,供以后新对象分配使用.
    • Shenandoah用以支持并行整理的核心概念–Brooks Pointer.
      Brooks Pointer不需要用到内存保护陷阱,而是在原有对象布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己.
      从结构上看,Brooks提出的转发指针于某些早期Java虚拟机使用过的句柄定位有一些相似之处,两者都是一种间接性的对象访问方式,差别是句柄通常会统一存储在专门的句柄池中,而转发指针是分散存放在每一个对象头前面.
    • 测试结果:弱项(高运行负担使得吞吐量下降),强项(低延迟时间).

2.ZGC收集器

  • 概括:ZGC收集器是一款基于Region内存分布的,不设分代的,使用了读屏障,染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器.

  • 另一个优点:支持"NUMA-Aware"的内存分配(一种为多处理器或者多核处理器的计算机所设计的内存架构).

  • 缺点:放弃了记忆集,写屏障,分代等技术,限制了它能承受的对象分配速率不会太高.

  • 内存布局:ZGC的Region(在官方文档中称为Page或者ZPage)具有动态性–动态创建和销毁,以及动态的区域容量大小.

    1. 小型Region:容量固定为2MB,用于放置小于256KB的小对象.
    2. 中型Region:容量固定为32MB,用于放置大于等于256KB但小于4MB的对象.
    3. 大型Region:容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象.大型Region在ZGC的实现中是不会被重分配,因为复制一个大对象的代价非常高昂.
    • ZGC的核心问题–并发整理算法的实现.同样使用读屏障,但与Shenandoah完全不同.

    • ZGC收集器有一个标志性的设计是它采用的染色指针技术.它会直接把标记信息记在引用对象的指针上,这时,与其说可达性分析时遍历对象图来标记对象,还不如说时遍历"引用图"来标记"引用".

    • 染色指针的三大优势:

    1. 染色指针可以使得一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理.
    2. 染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,设置内存屏障,尤其是写屏障的目的通常是为了记录对象引用的变动情况,如果将这些信息直接维护在指针中,显然就可以省去一些专门的记录操作.
    3. 染色指针可以作为一种可扩展的内存结构用来记录更多与对象标记,重定位过程相关的数据,以便日后进一步提高性能.
    • 重新定义内存中某些指针的其中几位,操作系统是否支持?
      答:提供了虚拟内存映射技术.

    • ZGC的运作过程?

    1. 并发标记:与G1一样,不同的是ZGC的标记是在指针上,标记阶段会更新染色指针中的Marked 0,Marked 1标志位.
    2. 并发预备重分配:需要根据特定的查询条件统计得出本次收集过程要清理那些Region,将这些Region组成重分配集.(不同于G1,会扫描全部的Region,用范围更大的扫描换取省去G1中记忆集的维护成本).因此,ZGC的重分配集只是决定了里面的存活对象会被重新复制到其他的Region中,里面的Region会被释放,而并不能说回收行为就只能是针对这个集合里面的Region进行,因为标记过程是针对全堆的.在JDK 12的ZGC中开始支持的类卸载以及弱引用的处理,也是在这个阶段完成的.
    3. 并发重分配:把重分配及中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表,记录从就对象到新对象的转向关系.当遇到并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正该更引用的值,使其直接指向新对象,ZGC这种行为成为指针的"自愈"能力.
    4. 并发重映射 :修正整个堆中指向重分配集中就对象的所有引用.

7.Epsilon收集器

  • 是JDK 11中出现的,一款以不能够进行垃圾收集为"卖点"的垃圾收集器.
    适用于运行负载极小,没有任何回收行为的收集器.

8内存分配与回收策略

Java技术体系的自动内存管理,最根本的目标是自动化的解决两个问题:自动给对象分配内存以及自动回收分配给对象的内存.

1.对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配.当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC.

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

容易导致明明还有不少空间就提前触发垃圾收集,以获取足够连续的空间才能安置他们,而当复制对象时,大对象就意味着高额的内存复制开销.

HotSpot虚拟机提供了-XX:PretenureSizeThreshold参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区之间来回复制,产生大量的内存复制操作.

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

对象通常在Eden区里诞生,如果经过第一次Minor GC后仍然存货,并且能被Survivor容纳的话,该对象会被移动到Survivor空间中,并且将其对象年龄设为1岁.对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15),就会被晋升为老年代.对象晋升为老年代的年龄阈值参数:-XX:MaxTenuringThreshold设置.

4.动态对象年龄判定

HotSpot虚拟机并不是永远要求对象的年龄必须达到-XX:MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象的大小总和大于Survivor空间的一般,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到 -XX:MaxTenuringThreshold中要求的年龄.

5.空间分配担保

在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次Minor GC可以确保时安全的.如果不成立,则虚拟机会先查看-XX:HandlePromotionFailure参数的设置值是否允许担保失败;如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的可如果小于,或者-XX:HandlePromotionFailuer设置不允许冒险,那这时就要改为进行一次Full GC.

JVM--垃圾收集器与内存分配策略相关推荐

  1. jvm垃圾收集器与内存分配策略

    2019独角兽企业重金招聘Python工程师标准>>> 垃圾收集器与内存分配策略: 以下参考周志明的<<深入理解jvm高级特性与最佳实践>>. 判断对象是否存 ...

  2. JVM:垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 1.对象已死吗 1).引用计数算法 引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就 ...

  3. java eden分配参数,JVM垃圾收集器与内存分配策略,

    垃圾收集器与内存分配策略 对象存活判断 引用计数算法 给对象添加一个计数器,每有一个引用+1,当引用失效-1,若为0则不在被使用. 可达性分析算法 对象是否可到达GC roots 或者说GC root ...

  4. JVM垃圾收集器与内存分配策略学习总结

    方法区: 1.线程共享 2.储存类信息,常量,静态变量,编译器编译后的代码 3.非堆(别名)用于区分Java堆 4.不需要连续的内存 5.可以固定或可扩张 6.选择不实现垃圾回收//这个区域很少进行垃 ...

  5. [JVM] 垃圾收集器与内存分配策略

    目录 简介 哪些内存需要回收 引用计数算法 可达性分析 再次谈引用 生存和死亡 简介 GC(Carbage Coolection),需要完成的3件事情 : 哪些内存需要回收 什么时候回收 如何回收 程 ...

  6. jvm(3)-垃圾收集器与内存分配策略

    [0]README 0.1)本文部分文字转自:深入理解jvm,旨在学习 垃圾收集器与内存分配策略 的基础知识: [1]垃圾回收概述 1)GC(Garbage Collection)需要完成的3件事情: ...

  7. 垃圾收集器与内存分配策略(五)之垃圾日志与常见参数

    2019独角兽企业重金招聘Python工程师标准>>> 垃圾收集器与内存分配策略(五)--垃圾日志与常见参数 理解GC日志 每个收集器的日志格式都可以不一样,但各个每个收集器的日志都 ...

  8. 【深入理解Java虚拟机学习笔记】第三章 垃圾收集器与内存分配策略

    最近想好好复习一下java虚拟机,我想通过深读 [理解Java虚拟机 jvm 高级特性与最佳实践] (作者 周志明) 并且通过写一些博客总结来将该书读薄读透,这里文章内容仅仅是个人阅读后简短总结,加强 ...

  9. java_opts gc回收器_jvm垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略: 以下参考周志明的<>. 判断对象是否存活: 引用计数:通过判断对象被引用的次数(为0,则表示不可被使用),但这很难解决对象相互循环引用的问题. 根搜索算法:即采 ...

  10. 7种垃圾收集器与内存分配策略,看这一篇就够了

    垃圾收集器与内存分配策略-垃圾收集器 (A).图中展示了7种不同分代的收集器: Serial.ParNew.Parallel Scavenge.Serial Old.Parallel Old.CMS. ...

最新文章

  1. JSBridge 技术原理分析
  2. 字体大小的设置_老年人用智能手机,怎样更改字体大小?3种方法,手把手教您学会...
  3. Ext2、Ext3和Ext4之间的区别
  4. 程序员35岁之后的出路_我35岁,被裁员,开始给程序员规划人生
  5. REVERSE-PRACTICE-BUUCTF-15
  6. (十三)nodejs循序渐进-高性能游戏服务器框架pomelo之扩展聊天服务器为机器人自动聊天
  7. SVN汉化包安装后无效果(已解决)
  8. 友盟+短视频行业研究
  9. Centos 7 安装 TEMPO2
  10. 计算机系统维护要不要自动更新,电脑自动更新功能开启还是关闭,到底要不要关闭...
  11. 【APP推荐】手机制作微电影
  12. iphone6s html5没声音,iphone6s没有声音了怎么办(解决苹果机来电没声音的3种方式)...
  13. android-studio推荐模拟器,Android studio 三大模拟器比较,强烈推荐第三种
  14. 小米手机 5 开启【开发者选项】
  15. Leaflet 可视化--风场、海浪、洋流、气压、温度等
  16. java检验电话或传真_对有java开发过程 常用的js验证数字、电话号码、传真、邮箱、手机号码、邮编、日期...
  17. Jenkins升级大坑-插件问题
  18. 【Linux 将普通用户改为root用户】并授予某个目录或文件的所有权限
  19. LLVM 之父 Chris Lattner:模块化设计决定 AI 前途,不服来辩
  20. 1.小象笔记--汇率换算

热门文章

  1. 为虚构的游戏世界设计文字
  2. 华为云桌面Workspace,实惠更实用!
  3. 速看!TIOBE12月编程语言排行榜,第一名太牛啦!
  4. 设计模式探索之责任链模式
  5. win8连接WiFi受限解决方案
  6. VeeValidate 中文文档-Guide
  7. python中怎么输出双引号
  8. JAVA语言入门详细知识点
  9. knn算法实例python_kNN算法及其Python实例
  10. egrep扩展正则表达式