Java内存回收 & 垃圾收集算法 & 垃圾收集器

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。

本文重点:

  • 二、对象已死?(如何判断对象是否存活(回收已死亡的对象))
  • 三、垃圾收集算法
  • 五、垃圾收集器

一、概述

垃圾收集器(Garbage Collection, GC)

程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每个栈帧中分配多少内存基本上是在类结构确定下来是就已知的。因此这几个区域的内存分配和回收都具备确定性,不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就随着回收了。

Java堆和方法区则有着显著的不确定性

二、对象已死?

堆中存放这几乎所有的实例对象,垃圾收集器在对堆进行回收之前,首先要确定哪些还“存活”着,哪些已经“死去”。

(死去: 即不可能再被任何途径使用到的对象)

1. 引用计数算法(Reference Counting)

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。

优点:原理简单,判定效率也很高

缺点:这个看似简单的算法有很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作。譬如单纯的引用计数就很难解决对象之间相互循环引用的问题。(在Java领域,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存)

testGC()方法:对象objA和objB都有字段instance,赋值令 objA.instance=objB及objB.instance=objA,除此之外,这两个对象再无任何引用,实际上这两个对象已 经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也 就无法回收它们。

2. 可达性分析算法

当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的

基本思路:就是通过一系列称为 “GC Roots” 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的

在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:

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

  • 方法区类静态属性引用的对象,譬如Java类的引用类型静态变量。

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

  • 本地方法栈JNI(Java Native Interface,即通常所说的Native方法引用的对象

  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

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

  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。

内存区域是虚拟机自己的实现细节(在用户视角里任何内存区域都是不 可见的),更不是孤立封闭的,所以某个区域里的对象完全有可能被位于堆中其他区域的对象所引用,这时候就需要将这些关联区域的对象也一并加入GC Roots集合中去,才能保证可达性分析的正确性。

3. 引用

在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为:

  • 强引用(Strongly Re-ference)

    • 传统的引用定义,指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。
  • 软引用(Soft Reference)

    • 一些还有用,但非必须的对象。
    • 只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。
    • 在JDK 1.2版之后提供了SoftReference类来实现软引用。
  • 弱引用(Weak Reference)

    • 那些非必须对象,但是它的强度比软引用更弱一些

    • 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够都会回收掉只被弱引用关联的对象

    • WeakReference类来实现弱引用

  • 虚引用(Phantom Reference)

    • 为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。

    • 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知

    • PhantomReference类来实现虚引用。

Phantom [fæntəm] n.

这4种引用强 度依次逐渐减弱。

4. 生存还是死亡?

即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓刑”阶段

真正宣告一个对象死亡,至少要经历两次标记过程

  1. 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记

  2. 随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法

    • 假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。
    • 如果这个对象被判定为确有必要执行finalize()方法,那么该对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法。

    对象可以在被GC时自我拯救

    这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次

    如果对 象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集 合;如果对象这时候还没有逃脱,那基本上它就真的要被回收了。

5. 回收方法区

方法区的垃圾收集主要回收两部分内容:

  • 废弃的常量
  • 不再使用的类型

回收废弃常量与回收 Java堆中的对象非常类似。举个常量池中字面量回收的例子,假如一个字符串“java”曾经进入常量池中,但是当前系统又没有任何一个字符串对象的值是“java”,换句话说,已经没有任何字符串对象引用常量池中的“java”常量,且虚拟机中也没有其他地方引用这个字面量。如果在这时发生内存回收,而且垃圾收集器判断确有必要的话,这个“java”常量就将会被系统清理出常量池。常量池中其他类(接口)、方法、字段的符号引用也与此类似。

判定一个类型是否属于“不再被使用的类”需要同时满足下面三个条件:

  1. 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。

  2. 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。

  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

三、垃圾收集算法

1. 分代收集理论

当前商业虚拟机的垃圾收集器,大多数都遵循了**“分代收集”(Generational Collection)的理论**进行设计

  1. 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
  2. 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消 亡。
  3. 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。

设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区 域之中存储。

在Java堆划分出不同的区域之后,垃圾收集器才可以每次只回收其中某一个或者某些部分的区域

“Minor GC”“Major GC”“Full GC”

  1. 标记-复制算法

  2. 标记-清除算法

  3. 标记-整理算法

新生代 & 老年代

新生代:标记-复制算法

老年代:标记-清除 或者 标记-整理算法

对象不是孤立的,对象之间会存在跨代引用。

存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。

(1)解决跨代引用:

我们就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构(该结构被称为“记忆集”,Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。虽然这种方 法需要在对象改变引用关系(如将自己或者某个属性赋值)时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。

通常能单独发生收集行为的只是新生代,所以这里“反过来”的情况只是理论上允许,实际上除了CMS收集器,其他都不存在只针对老年代的收集。

2. 标记-清除算法

  • 标记------判断对象是否属于垃圾
  • 清除

法1:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象

法2:也可以反过来,标记存活的对象,统一回收所有未被标记的对象。

缺点:

  1. 执行效率不高。大量标记和清除操作
  2. 内存空间碎片化问题。内存空间碎片化太多可能会提前触发另一次垃圾收集动作(需要分配较大对象时无法找到足够的连续内存)

3. 标记-复制算法(新生代垃圾收集)

半区复制:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

特点:实现简单,运行高效

缺点:空间浪费严重

更优化的版区复制策略—Apple式回收

新生代 = Eden空间 + From Survivor空间 + To Survivor空间 (8 : 1 : 1)

or 新生代 = Eden空间 + 2 * Survivor空间

每次分配内存只使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。

HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,即每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会被“浪费”的。

当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保(Handle Promotion)。 (一般就将它们复制到老年代去)

缺点:

  1. 对象存活率较高时就要进行较多的复制操作,效率将会降低
  2. 如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

4. 标记-整理算法

首先标记出所有存活的对象,是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存

缺点如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行。

但如果跟标记-清除算法那样完全不考虑移动和整理存活对象的话,弥散于堆中的存活对象导致的空间碎片化问题就只能依赖更为复杂内存分配器和内存访问器来解决。内存访问是用户最频繁的操作(大量内存访问势必会直接影响应用程序的吞吐量)

移动则内存回收时会更复杂,不移动则内存分配时会更复杂。从垃圾收集的停顿时间来看,不移动对象停顿时间会更短,甚至可以不需要停顿,但是从整个程序的吞吐量来看,移动对象会更划算。

即使不移动对象会使得收集器的效率提升一些,但因内存分配和访问相比垃圾收集频率要高得多,这部分的耗时增加,总吞吐量仍然是下降的。HotSpot虚拟机里面关注吞吐量Parallel Scavenge收集器是基于标记-整理算法的,而关注延迟CMS收集器则是基于标记-清除算法的,这也从侧面印证这点

一种“和稀泥式”解决方案可以不在内存分配和访问上增加太大额外负担,做法是让虚拟机平时多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配时再采用标记-整理算法收集一次,以获得规整的内存空间。前面提到的基于标记-清除算法的CMS收集器面临空间碎片过多时采用的就是这种处理办法。

JNI----Java Native Interface

四、HotSpot的算法具体细节实现

这部分仅供了解,不感兴趣的读者可直接跳过

6. 并发的可达性分析

保障一致性

收集器在对象图上标记颜色,同时用户线程在修改引用 关系——即修改对象图的结构,这样可能出现两种后果。

  1. 一种是把原本消亡的对象错误标记为存活, 这不是好事,但其实是可以容忍的,只不过产生了一点逃过本次收集的浮动垃圾而已,下次收集清理 掉就好。
  2. 另一种是把原本存活的对象错误标记为已消亡,这就是非常致命的后果了,程序肯定会因此发生错误

当且仅当以下两个条件同时满足时,会产生“对象消失”的问题,即原本应该是黑色的对象被误标为白色:

  1. 赋值器插入了一条或多条从黑色对象到白色对象的新引用;

  2. 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。

我们要解决并发扫描时的对象消失问题,只需破坏这两个条件的任意一个即可。

由此分别 产生了两种解决方案:增量更新(Incremental Update)和原始快照(Snapshot At The Beginning,SATB)。

增量更新:要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫 描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。

原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。

以上无论是对引用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。

在HotSpot虚拟机中,增量更新和原始快照这两种解决方案都有实际应用,譬如,CMS是基于增量更新来做并发标记的,G1、Shenandoah则是用原始快照来实现。

五、经典垃圾收集器

剩下的部分了解即可

如果两个收集器之间存在连线,就说明它们可以搭配使用,图中收集器所处的区域,则表示它是属于新生代收集器抑或是老年代收集器。

1. Serial收集器

serial:adj:一系列的,连续的,顺序排列的,单线程的

  • 单线程
  • 新生代收集器
  • 标记-复制算法

Serial收集器依然是HotSpot虚拟机运行在客户端模式下的默认新生代收集器,有着优于其他收集器的地方,那就是简单而高效(与其他收集器的单线程相比),对于内存资源受限的环境,它是所有收集器里额外内存消耗(Memory Footprint)最小的;对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率

2. ParNew收集器

  • 多线程
  • 新生代收集器
  • 标记-复制算法

Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余与Serial收集器完全一致

它是不少运行在服务端模式下的HotSpot虚拟机,尤其是JDK 7之前的遗留系统中首选的新生代收集器

除了Serial收集器外,目前只有ParNew收集器能与CMS收集器(老年代的收集器)配合工作

并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。

并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

3. Parallel Scavenge收集器

Parallel: adj.平行的,并行的

Scavenge: [ˈskævɪndʒ] v. (从废弃物中)觅食; 捡破烂; 拾荒;

  • 新生代收集器
  • 标记-复制算法
  • 能够并行收集的多线程收集器
  • 关注点不同:Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)(“吞吐量优先收集器”)
    • CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间
  • 自适应的调节策略(GC Ergonomics):把内存管理的调优任务交给虚拟机去完成也许是一个很不错的选择。只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用-XX:MaxGCPauseMillis参数(更关注最大停顿时间)或-XX:GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了

停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验

高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务

吞吐量:就是处理器用于运行用户代码的时间与处理器总消耗时间的比值

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

ratio:n.比率,比例

是如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器以外别无选择,其他表现良好的老年代收集器,如CMS无法与它配合工作

4. Serial Old收集器

  • Serial的老年代版本
  • 单线程收集器
  • 标记-整理算法

供客户端模式下的HotSpot虚拟机使用。如果在服务端模式下,它也可能有两种用途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用,另外一种就是作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。

5. Parallel Old收集器

直到JDK 6时才开始提供的。

Parallel Scavenge收集器架构中本身有PS MarkSweep收集器来进行老年代收集,并非直接调用Serial Old收集器,但是这个PS MarkSweep收集器与Serial Old的实现几乎是一样的

  • Parallel Scavenge收集器的老年代版本
  • 支持多线程并发收集
  • 基于标记-整理算法实现

直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的搭配组合,在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合

6. CMS收集器

Concurrent Mark Sweep

  • 标记-清除算法(可有名字看出)
  • 并发收集
  • 低停顿

是一种以获取最短回收停顿时间为目标的收集器。

目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验

  1. 初始标记(CMS initial mark) ------“Stop the World”

    • 仅仅只是标记一下GC Roots能直接关联到的对象,速度很快
  2. 并发标记(CMS concurrent mark)
    • 从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
  3. 重新标记(CMS remark)-------------“Stop the World”
    • 为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短
  4. 并发清除(CMS concurrent sweep)
    • 清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发

由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说CMS收集器的内存回收过程是与用户线程一起并发执行的

缺点:

  1. **CMS收集器对处理器资源非常敏感。**事实上,面向并发设计的程序都对处理器资源比较敏 感。在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的计算能力)而导致应用程序变慢,降低总吞吐量。
  2. CMS收集器无法处理“浮动垃圾”(Floating Garbage),有可能出现“Con-current Mode Failure”(并发失败)失败进而导致另一次完全“Stop The World”的Full GC的产生
    • 浮动垃圾:在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉
    • 必须预留一部分空间供并发收集时的程序运作(用户线程)使用
  3. 碎片化空间。(标记-清除算法)空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次Full GC的情况。

7. Garbage First收集器

简称G1:开创了收集器面向局部收集的设计思路和基于Region的内存布局形式

  • 面向服务端
  • 为CMS收集器的替代者和继承人
  • 将堆内存“化整为零”
  • 可以指定最大停顿时间、分Region的内存布局、按收益动态确定回收集这

JDK 9发布之日,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器,而CMS则沦落至被声明为不推荐使用(Deprecate)的收集器

Deprecate : [ˈdeprəkeɪt] v.不赞成,强烈反对

在G1收集器出现之前的所有其他收集器,包括CMS在内,垃圾收集的目标范围要么是整个新生代(Minor GC),要么就是整个老年代(Major GC),再要么就是整个Java堆(Full GC)。而G1跳出了这个樊笼,它可以面向堆内存任何部分来组成回收集(Collection Set,一般简称CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。

G1开创的基于Region的堆内存布局是它能够实现这个目标的关键。G1不再坚持固定大小以及固定数量的 分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的 Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待,

  • 记忆集
  • 原始快照(解决用户线程改变对象引用关系的问题)

如果内存回收的速度赶不上内存分配的速度,G1收集器也要被迫冻结用户线程执行,导致Full GC而产生长时间“Stop The World”。

G1为每一个Region设计了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。G1收集器默认在这个地址以上的对象是被隐式标记过的,即默认它们是存活的,不纳入回收范围。

G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的

可以由用户指定期望的停顿时间是G1收集器很强大的一个功能,设置不同的期望停顿时间,可使得G1在不同应用场景中取得关注吞吐量和关注延迟之间的最佳平衡。

它默认的停顿目标为两百毫秒。

六、低延迟垃圾收集器

衡量垃圾收集器的三项最重要的指标是:

  1. 内存占用(Footprint)
  2. 吞吐量(Throughput)
  3. 延迟(Latency)

延迟的重要性日益凸显,越发备受关注。其原因是随着计算机硬件的发展、性能的提升,我们越来越能容忍收集器多占用一点点内存;硬件性能增长,对软件系统的处理能力是有直接助益的,硬件的规格和性能越高,也有助于降低收集器运行时对应用程序的影响,换句话说,吞吐量会更高。但对延迟则不是这样,硬件规格提升,准确地说是内存的扩大,对延迟反而会带来负面的效果,这点也是很符合直观思维的:虚拟机要回收完整的1TB的堆内存,毫无疑问要比回收1GB的堆内存耗费更多时间。

浅色阶段表示必须挂起用户线程,深色表示收集器线程与用户线程是并发工作的

Shenandoah和ZGC,几乎整个工作过程全部都是并发的,只有初始标记、最终标记这些阶段有短暂的停顿,这部分停顿的时间基本上是固定 的,与堆的容量、堆中对象的数量没有正比例关系。这两款目前仍处于实验状态的收集器,被官方命名为“低延迟垃圾收集器”(Low-Latency Garbage Collector或者Low-Pause-Time Garbage Collector)。

深入理解Java虚拟机(二):Java内存回收及垃圾收集算法相关推荐

  1. Java虚拟机学习(2):垃圾收集算法

    跟踪收集器 跟踪收集器采用的为集中式的管理方式,全局记录对象之间的引用状态,执行时从一些列GC  Roots的对象做为起点,从这些节点向下开始进行搜索所有的引用链,当一个对象到GC  Roots 没有 ...

  2. 深入理解java虚拟机(三)GC垃圾回收-对象存活算法

    文章目录 前言 一.引用计数算法 二.可达性分析算法 三.了解引用 结尾 前言 在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还&qu ...

  3. 深入理解java虚拟机(五)GC垃圾回收-经典垃圾收集器

    文章目录 前言 一.Serial收集器(标记-复制算法) 二.ParNew收集器(标记-复制算法) 三.Parallel Scavenge收集器(标记-复制算法) 四.Serial Old收集器(标记 ...

  4. 深入理解Java虚拟机:JVM内存管理与垃圾收集理论

    文章目录 阅读的疑问??? 第二部分 自动内存管理 第2章 Java内存区域与内存溢出异常 1.程序计数器 2.Java虚拟机栈 3.本地方法栈 4.Java堆 5.方法区(也即:永久代(PermGe ...

  5. 深入理解JVM虚拟机(二):垃圾回收机制

    谈起GC,应该是让Java程序员最激动的一项技术,我相信每个Java程序员都有探究GC本质的冲动!JVM垃圾回收机制对于了解对象的创建和对象的回收极为重要,是每个Java程序员必须掌握的技能. 本博客 ...

  6. 深入理解Java虚拟机:jvm内存模型jdk1.8

    深入理解Java虚拟机:jvm内存模型jdk1.8 一.程序计数器 使用PC寄存器存储字节码指令地址有什么作用?为什么使PC寄存器记录当前线程的执行地址? PC寄存器为什么会被设定为线程私有? 二.J ...

  7. 深入理解java虚拟机 (二) 第二版

    如何阅读本书 本书-共分为五个部分:走近Java.自动内存管理机制.虛拟机执行子系统.程序编译与代码优化.高效并发.各部分基本上是互相独立的,没有必然的前后依赖关系,读者可以从任何- -个感兴趣的专题 ...

  8. 《深入理解Java虚拟机》阅读——垃圾回收机制

    <深入理解Java虚拟机>阅读--垃圾回收机制 前言 why--为什么需要垃圾回收 what--垃圾回收做些什么 where--去哪里回收垃圾 how--垃圾回收是怎么做的 垃圾是否要回收 ...

  9. 深入理解Java虚拟机:Java垃圾回收机制

    本篇内容包括:JAVA 垃圾回收机制概述.有哪些内存需要回收.如何回收(标记-清除.标记-整理(标记-清除-压缩).复制(标记-复制-清除).分代收集等算法) 以及 何时进行垃圾回收等内容! 一.概述 ...

最新文章

  1. JavaScript 学习中的帮助记忆(日积月累)
  2. 大话TreeMap的put,get过程
  3. Java编号姓名元宝数密码,通过my Eclipse控制台向数据库(SQL2008)中查找、删除、插入信息...
  4. Logistic regression Newton’s method
  5. jetbrick-template 和其他模板的性能测试比较
  6. 熊猫烧香当年到底有多残忍?
  7. [Microsoft][ODBC Microsoft Access Driver] 参数不足,期待是 1
  8. IIS 热启动设置的方法
  9. 1038: 绝对值最大
  10. AlphaPose配置最新教程
  11. 解决 required a bean of type ‘com.aliyun.oss.OSSClient‘ that could not be found
  12. 酷狗音乐QQ显示(VC源码)
  13. 李沐d2l《动手学深度学习》第二版——风格迁移源码详解
  14. LeetCode Relative Ranks
  15. 西邮Linux兴趣小组2019-2021年纳新面试题解析
  16. Python 函数 sort(),sorted() 之区别及 key=lambda x:x[] 之理解
  17. 小马哥-----高仿米4拆机 刷机主板多图展示 主板为x77 型号k6 6582芯片 14年底版本
  18. GSM模块联网 GPRS上传物联网云平台调试笔记
  19. 博途PLC 1200/1500PLC MODBUS-RTU通讯优化(状态机编程)
  20. 多卡聚合(多路聚合)5G + 4G LTE+Wifi融合通信方式

热门文章

  1. 什么是二极管的频率特性
  2. 标准C语言day02
  3. Movist Pro for mac如何同时查看多个字幕?
  4. 漫话:如何给女朋友解释为什么吴某凡会被中间人攻击?
  5. 师徒机制及其实施方案
  6. TeeChart8.0安装 ---转贴收藏
  7. Charle抓包测试-基础配置+实战
  8. STC8H1K08 - INT2 - 下降沿触发外部中断 2
  9. x64驱动:DKOM 实现进程隐藏
  10. 转 电信业务平台融合的探讨