第三章 垃圾收集器与内存分配策略

本文详细探讨了Java内存管理中的垃圾收集机制,包括引用计数、可达性分析、引用类型及其影响,以及分代收集理论、垃圾回收算法(如标记-复制、标记-整理)和典型收集器如Serial、ParallelScavenge、CMS和G1的特性和应用场景。还介绍了低延迟收集器选项和选择收集器的策略。
摘要由CSDN通过智能技术生成

3.1 概述

垃圾收集(Garbage Collection, GC)
当需要排查各种内存 溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术是是必要的监控和调节;

  • 程序计数器、虚拟机栈、本地方法栈随线程而生、而灭;内存分配和回收都具备确定性;当方法结束或者线程结束时,内存自然就跟随着回收了;
  • Java堆和方法区很不确定性:一个接口的多个实现类需要的内存可能会不一样;一个方法所执行的不同条件分支所需要的内存也可能不一样;只有运行时才知道;这部分内存的分配和回收是动态的;垃圾收集器所关注的也是这部分内存;

3.2 对象已死?

3.2.1 引用计数算法
  • 对象中添加一个引用计数器
  • 每当有一个地方引用他时,计数器值加一;
  • 当引用失效时,计数器值减一;
  • 难以解决对象之间相互循环引用的问题;
可达性分析算法

当前主流的商用程序语言的内存管理子系统,都是通过可达性分析算法来判定对象是否存活;

  • 基本思路:通过一系列称为“GC Roots”的跟对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”;从某个对象到GC Roots间没有任何引用链相连,或从GC Roots到这个对象不可达,此对象不可能再被使用;
    在这里插入图片描述
    Java里,固定可作为GC Roots的对象包括以下集中:
  • 在虚拟机栈中引用的对象;各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量;
  • 在方法区中类静态属性引用的对象;如Java类的引用类型静态变量;
  • 在方法区中常量引用的对象;如字符串常量池里的引用;
  • 在本地方法栈中JNI引用的对象;
  • Java虚拟机内部的引用;如基本数据类型对应的Class对象,一些常驻的异常对象,系统类加载器;
  • 所有被同步锁(synchronized关键字)持有的对象;
  • 反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等;
  • 其他的:分代收集、局部回收;如果只针对Java堆中某一块区域发起垃圾收集时,必须考虑到内存区域是虚拟机自己的实现细节 – 某个区域里的对象可能被位于堆中其他区域的对象所引用;
3.2.3 再谈引用

引用(传统定义):如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,称该reference数据是代表某块内存、某个对象的引用;
引用分为强引用、软引用、弱引用和虚引用;4种引用强度一次逐渐减弱

  • 强引用: 最传统的 “引用”定义,指在程序代码之中普片存在的引用赋值;类似“Object obj = new Object()”这种引用关系; 只要强引用关系还存在,垃圾收集器就永远不会收掉被引用的对象;
  • 软引用: 用来描述一些还有用,但非必须的对象;只被软引用关联的对象,在系统将要发生内存溢出前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常;
  • 弱引用: 描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存道下一次垃圾收集发生为止;当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象;
  • 虚引用:幽灵引用、幻影引用;最弱的一种引用关系;一个对象是否有虚引用存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。设置虚引用关联的唯一目的:为了能在这个对象被收集器回收时收到一个系统通知;
3.2.4 生存还是死亡?

可达性分析算法种判定为 不可达的对象,也不是“非死不可”;
之后有两次标记:

  • 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记;
  • 随后进行一次筛选,条件是此对象是否有必要执行finalize()方法;
  • 如果对象没有覆盖finalize方法,或者finalize方法已经被虚拟机调用过,那么没必要执行finalize方法;
  • 如果对象被判定为确有必要执行finalize方法,那么该对象将会被放置在一个名为F-Queue的队列之中,并在稍后有一条虚拟机自动建立的、低调度优先级的Finalizer线程去执行他们的finalize()方法。
  • **: 执行指 虚拟机会触发这个方法执行,并不一定会等待他运行结束
    • 如果finalize方法执行缓慢、极端死循环,将导致F-Queue队列中的其他对象永久处于等待,升值导致整个内存回收子系统崩溃。

finalize方法是对象逃脱死亡命运的最后一次机会(在finalize里 给对象 加个引用;但finalize只能执行一次,只能逃脱一次):

  • 稍后收集器将对F-Queue中的对象进行第二次小规模的标记;
  • 如果对象要在finalize中拯救自己-- 只要重新与引用链上任何一个对象建立关联即可;如把自己赋值给某个类变量或者对象的成员变量;在第二次标记时它将被移出“即将回收”的集合;
  • 如果对象这时候还没有逃脱,那基本上他就真的要被回收了;
3.2.5 回收方法区
  • Java堆中,尤其是新生代中,对常规应用进行一次垃圾收集通常可以回收70%到99%的内存空间;
  • 相比之下,方法区回收囿于苛刻的判定条件,回收成果往往远低于此;

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。

  • 废弃的常量:与Java堆中的对象非常类似;例子:“java”曾经进入常量池中,但当前系统没有任何一个字符串对象的值是“java” – 已经没有任何字符串对象引用常量池中的“java”常量,而且虚拟机中也没有其他地方引用这个字面量;这时候发生内存回收,判定有必要的话,他就会被清理出常量池; 常量池中其他类(接口)、方法、字段的符号引用也与此类似;
  • 判定一个常量是否“废弃” 还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就比较苛刻,需要同时满足三个条件:
    • 1、该类所有的实例都已经被回收;不存在该类以及任何派生子类的实例;
    • 2、加载该类的类加载器已经被回收;除非是精心设计的可替换类加载器的场景,否则很难达成;
    • 3、该类对应的java.lang.Class对象没有在任何地方被引用;无法在任何地方通过反射访问该类的方法;
      满足三个条件,被允许回收; 不是像对象一样 必须回收;

3.3 垃圾回收算法

介绍分代收集理论和集中算法思想及其发展过程;
垃圾收集算法可以划分为“引用计数式垃圾收集(Reference Counting GC)”和 “追踪式垃圾收集”(Tracing GC)两大类;
这两类常被称为“直接垃圾收集”和“间接垃圾收集”;
下面介绍的算法 都属于 追踪式垃圾收集的范畴;

3.3.1 分代收集理论

当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”的理论进行设计;
分代收集:实质是一套符合大多数程序运行实际情况的经验法则;建立在两个分代假说之上:

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

这两个分代假说共同奠定了多款常用的垃圾收集器的一致设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(熬过垃圾收集过程的次数)分配到不同的区域中存储;

  • 如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程,那么他们集中放在一起,每次回收时职官志如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量空间;
  • 如果剩下的都是难以消亡的对象,把他们集中一块;虚拟机便可以使用较低的频率来回收这个区域;这就同时兼顾了啊垃圾收集的时间开销和内存空间有效利用;

Java堆分区影响:

  • Java堆划分不同区域后,垃圾收集器才可以每次只回收其中一个或者某些部分区域–因而才有了“Minor GC”、“Major GC”、“Full GC”这样的回收类型划分;
  • 也能够针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法;
  • 因而发展出了“标记-复制算法”、“标记-清除算法”、“标记-整理算法”等针对的垃圾收集算法;

商用Java虚拟机,设计者一般至少把Java堆划分为新生代和老年代两个区域;

  • 新生代:每次垃圾收集时有大批对象死去,存活的少量对象会逐步晋升到老年代中存放;

分代收集的困难: 对象不是孤立的,对象之间会存在跨代引用;

  • 加入要进行一次只局限于新生代区域内的收集(Minor GC),但新生代的对象完全有可能被老年代所引用;为了找出该区域的存活对象,不得不在固定的GC Roots之外,再额外遍历整个老年代中所有对象来确保可达性分析结果正确性;反过来也一样; 这样会增加性能负担

由此需要添加第三条经验法则:
**跨代引用假说:**跨代引用相对于同代引用来说仅占极少数;
依据此:不应该再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用;只需要在新生代上建立一个全局的数据结构(记忆集,Remebered Set)把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用;
此后发生Minor GC,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots及进行扫描;

名词定义:

  • 部分手机(Partial GC): 目标不是完整收集整个Java堆的垃圾收集
    • 新生代收集(Minor GC/Young GC):目标只是新生代的垃圾收集;
    • 老年代收集(Major GC/Old GC): 目标只是老年代垃圾收集; 目前只有CMS收集器会有到单独收集老年代的行为;
    • 混合收集(Mixed GC): 目标是收集整个新生代以及部分老年代的垃圾收集;目前只有G1收集器会有这种行为;
  • 整堆收集(Full GC): 收集整个Java堆和方法区的垃圾收集;
3.3.2 标记-清除算法

算法分为“标记” 和 “清除”两个阶段:

  • 首先标记处所有需要回收的对象;
  • 标记完成后,统一回收掉所有被标记的对象;
  • (反过来,标记存活对象,清除未被标记对象)

标记过程就是对象是否属于垃圾的判定过程;前面记录好了;

缺点:

  • 执行效率不稳定; 如果Java堆中包含大量对象,且其中大部分是需要被回收的,这时需要大量标记 和 清除的动作, 导致效率随 对象数量增加而降低;
  • 内存空间碎片化问题: 标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能导致以后再程序运行过程中 需要分配较大对象时无法找到足够的连续内存而不得不提前出发另一次垃圾收集动作;
    在这里插入图片描述
3.3.3 标记-复制算法(新生代)
  • 简称复制算法;
  • 为了解决标记-清除 算法面对大量可回收对象时执行效率低的问题;
  • “半区复制”的垃圾收集算法: 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块;
  • 当一块内存用完了,就将还存活着的对象复制到另一块上面,然后把一使用过内存空间一次清理掉;
  • 如果存活对象过多,开销大;代价:一半内存;

商用Java虚拟机优先采用这种收集算法去回收新生代;
IBM研究: 新生代中对象有98%熬不过第一轮收集,因此并不需要按照1:1的比例来划分新生代的内存空间;
在这里插入图片描述

优化: Appel式回收

  • 把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只是用Eden和其中一块Survivor;
  • 当发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间;
  • HotSpot默认 Eden和Survivor大小比例是 8 : 1;
  • 逃生门的安全设计: 当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域进行分配担保(大多是时老年代);

内存分配担保 好比我们去银行借款;如果另一块Survivor空间不够,多出来的对象将通过分配担保机制直接进入老年代,这对虚拟机来说是安全的;

3.3.4 标记-整理算法(老年代)

标记-复制算法在对象存活率较高时就要进行较多的赋值操作,效率将会降低;更关键的50%空间浪费,还得要有额外的空间进行分配担保;

  • 针对老年代的存亡特征
  • 标记过程与 标记-清除算法一样;
  • 然后,让所有存活的对象像内存空间一段移动,然后直接清理掉边界以外的内存;
    在这里插入图片描述
    “Stop The World”: 移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作;这种对象移动操作必须全程暂停用户应用程序才能进行;更加让使用者不得不小心翼翼地权衡利弊了;
    标记-清除的空间碎片化 更难处理; 分区空闲分配链表等;
    基于以上两点,是否移动对象都存在弊端:
  • 移动,则内存回收时会更复杂;
  • 不移动则内存分配时会更复杂;
  • 移动对象会更划算

和稀泥式方案: 虚拟机平时多数时间采用标记-清除算法;知道碎片化达到一定程度,采用标记-整理算法一次,以获得规整的内存空间; 如CMS收集器;

3.4 HotSpot算法细节实现

枯燥,

3.4.1 根节点枚举

可达性算法,从GCRoot 集合找引用链; 来介绍

  • 固定可作为GCRoots的节点主要在全局性的引用(如常量或类静态属性)与执行上下文(栈帧中的本地变量表)中;
  • 所有收集器在根节点枚举这一步骤都必须暂停用户线程的;
  • 可达性分析算法耗时最长的查找引用链过程已经可以做到与用户线程一起并发;但根节点枚举始终还是必须在一个能保障一致性的快照中才得以进行;
  • 一致性:整个枚举期间执行子系统看起来就像被冻结在某个时间点上,分析结果准确性也就无法保证;

主流Java虚拟机都是用准确式垃圾收集;当用户线程停顿下来之后,并不需要一个不漏的检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得到那些地方存放着对象引用的;

  • 在hotspot的解决方案里,是使用一组称为OopMap的数据结构;一旦类加载动作完成的时候,hotspot就会把对象内什么偏移量上是什么类型的数据计算出来;
  • 在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用;
    这样收集器扫描时就可以直接得知这些信息了;并不需要真正一个不漏的从方法区等GCRoots开始查找;
3.4.2 安全点
3.4.3 安全区域
3.4.4 记忆集与卡表
3.4.5 写屏障
3.4.6 并发的可达性分析

3.5 经典垃圾收集器

在这里插入图片描述
展示了七种作用域不同分代的收集器;

  • 如果两个收集器之间存在连线,就说明他们可以他陪使用;
  • 区域:新生代、老年代
    会介绍收集器的 目标、特性、原理和使用场景; 重点分析CMS和G1这两款相对复杂而又广泛使用的收集器;

还没有万能收集器 – 只是对具体应用选择最合适的收集器;

3.5.1 Serial收集器
  • 最基础、历史最悠久的收集器
  • 单线程工作的收集器; 只会使用一个处理器或一条收集线程去完成垃圾收集工作;必须暂停其他工作,直到收集结束;

在这里插入图片描述
优势:

  • 简单高效;
  • 额外内存消耗 最小的;
  • 单处理器或处理器核心数少的环境,有最高的收集效率;
  • 对于运行在客户端模式下的虚拟机来说 ,是很好的选择;
3.5.2 ParNew收集器
  • 是Serial收集器的多线程并行版本;
  • 除了同时使用多条线程进行垃圾收集之外;其余行为包括Serial收集器可用的所有控制参数、收集算法、StopTheWorld、对象分配规则、回收策略等都与Serial收集器完全一致;
    在这里插入图片描述
  • 除了支持多线程并行收集之外;并没有太多创新;
  • 是不少运行在服务器端模式下的HotSpot虚拟机;
  • 除了Serial收集器外,目前只有它能与CMS收集器配合工作;
3.5.3 Parallel Scavenge收集器
  • 新生代收集器; 基于标记-复制算法实现的收集器;
  • 能够并行收集的多线程收集器
  • 目标: 达到一个可控制的吞吐量;
    在这里插入图片描述
    停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序;
    高吞吐量可以最高效利用处理器资源,尽快完成程序的运算任务;

两个参数,精确控制吞吐量:

  • 最大垃圾收集停顿时间:-XX: MaxGCPauseMillis
    允许的值是一个大于0的毫秒数;尽力保证内存回收花费的时间不超过用户设定值;
    垃圾收集停顿时间缩短 是以牺牲吞吐量和新生代空间为代价换取的;
  • 直接设置吞吐量大小:-XX:GCTimeRatio
    大于0小于100的整数;也就是i垃圾收集时间占总时间的比率;
    例如:19 -》 1/(1+19)占5%;
3.5.4 Serial Old收集器
  • 是Serial收集器的老年代版本
  • 单线程收集器
  • 标记-整理算法
  • 供客户端模式下hotspot虚拟机使用
  • 服务端下两用途
    • JDK5及以前与Parallel Scavenge收集器搭配使用
    • 作为CMS收集器发生失败时的后备预案;
      在这里插入图片描述
3.5.5 Parallel Old收集器
  • 是Parallel Scavenge收集器的老年代版本, 支持多线程并发收集;基于 标记-整理算法
  • 吞吐量优先 / 处理器资源较为稀缺:优先考虑Parallel Scavenge+ Parallel Old 的搭配组合 ;
    在这里插入图片描述
3.5.6 CMS 收集器(Concurrent Low Pause Collector)
  • 是一种获取最短回收听段时间为目标的收集器;
  • 很大一部分Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上;较为关注响应速度,希望系统停顿时间尽可能短,给用户带来良好的交互体验;
  • 标记-清除,运作分为四个步骤
    • 1、初始标记CMS initial mark: 仅仅是标记一下GCRoots能直接关联到的对象,速度很快;
    • 2、并发标记CMS concurrent mark:从GCToors的直接关联对象开始遍历整个对象图的过程,耗时长;不需要停顿用户线程,并发运行;
    • 3、重新标记CMS remark: 修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录;停顿时间比初始标记阶段长一些,但远比并发标记短
    • 4、并发清除CMS concurrent sweep:清理删除掉标记阶段判断已经死亡的对象;与用户线程并发;
      初始标记和重新标记,需要 StopTheWorld;

由于耗时最长的 并发标记和并发清除 是与用户线程并发的,总体上,CMS收集器的内存回收过程是与用户线程一起并发执行的。
在这里插入图片描述
三个明显的缺点:

  • CMS收集器对处理器资源非常敏感(面向并发设计的都敏感)。
    在并发阶段,虽然不会导致用户线程停顿,但会降低总吞吐量;
  • 由于CMS收集器无法处理“浮动垃圾”,可能导致Full GC产生;
    浮动垃圾:CMS并发标记和并发清理阶段,产生新的垃圾对象;标记结束后产生的只能等待下一次收集;
    由于还要持续运行,就需要预留足够内存空间给用户线程使用;不是等满了才收集;
    如果CMS收集器启动阈值过高,CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”; 预案:冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了;
  • 基于标记-清除算法, 产生大量 内存碎片; 若干次 不整理空间, 一次 整理碎片;
3.5.7 Garbage First(G1)收集器
  • 里程碑式的成果,开创了收集器 面向局部收集的设计思路和基于Region的内存布局形式;
  • 主要面向服务端应用的垃圾收集器。
  • G1面向堆内存任何部分来组成回收集 进行回收;那块内存回收收益最大 Mixed GC模式
  • 基于Region的堆内存布局; 将Java堆划分为多个大小相等的独立区域,每个区域根据需要,扮演新生代的Eden空间、Survivor空间或者老年代空间等;收集器对扮演不同角色的Region采用不同的策略去处理;
  • Region有一类特殊的Humongous区域,专门用来存储大对象。
    G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象;
    超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中;
    G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待;
    在这里插入图片描述

虽然G1保留新生代和老年代的概念,但他们不在固定,他们都是一系列区域的动态集合;

  • 将Region作为单次回收的最小单元;每次收集的内存空间都是Region大小的整数倍;有计划避免在整个java堆中进行安全域的垃圾收集;
  • 让G1收集器去跟踪各个Region里面垃圾堆积的价值大小; 价值是回收所获得的空间大小以及回收所需时间的经验值;然后维护一个优先级列表,优先处理回收价值收益最大的那些Region;

要注意很多细节问题:

  • 如 将Java堆分成多个独立Region后,Region里面存在的跨Region引用对象如何解决; 思路:使用记忆集避免全堆作为GCRoots扫描; G1每个Region都维护有自己的记忆集,本质是一种哈希表,key是别的Region的起始地址,value是一个集合,存储的元素是卡表的索引号;双向卡表结构;
  • 如 并发阶段,如何保证收集线程和用户线程互不干扰的运行;
  • 如 怎么建立起可靠的停顿预测模型;

如果我们不去计算用户线程运行过程中的动作,G1收集器的运作过程大致可划分为以下四个步骤:

  • 初始标记Initial Marking: 仅仅只是标记一下GCRoots能直接关联到的对象,并修改TAMS指针的值,让下一阶段用户线程并发运行是,能正确地在可用的Region中分配新对象;耗时很短;
  • 并发标记 Concurrent Marking: 从GCRoot开始对队中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象;耗时长,与用户程序并发执行; 扫描完成后,要重新处理SATB记录下的在并发时有引用变动的对象;
  • 最终标记 Final Marking: 对用户线程做另一个短暂暂停,用于处理并发阶段结束后人遗留下来的最后少量的SATB记录;
  • 筛选回收Live Data Counting and Evacuation:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来定制回收计划;可自由选择任意多个Region构成回收集;

在这里插入图片描述
可以有用户指定期望的停顿时间是G1收集器很强大的一个功能;
设置不同的期望停顿时间,可使得G1在不同应用场景中却的关注吞吐量和关注延迟之间的最佳平衡;

3.6 低延迟垃圾收集器

衡量垃圾收集器的三个重要指标: 内存占用、吞吐量、延迟;
在这里插入图片描述

3.6.1 Shenandoah收集器
3.6.2 ZGC收集器

3.7 选择合适的垃圾收集器

3.7.1 Epsilon收集器
  • 以不能进行垃圾收集作为卖点
  • 如果 引用只要运行数分钟甚至数秒,只要Java虚拟机能正确分配内存,堆耗尽之前就会退出, 那显然 运行负载极小、没有任何回收行为的Epsilon很不错;
3.7.2 收集器的权衡

三个因素:

  • 应用程序的主要关注点是什么;如果是数据分析、科学计算类任务,吞吐量是关注点; 如果是SLA应用,那停顿时间直接影响服务质量,延迟;如果是客户端应用,垃圾收集的内存占用不可忽视;
  • 运行引用的基础设施如何? 硬件规格;处理器数量、分配内存大小;操作系统等;
  • 使用JDK的发行商是什么?版本号是什么?

例子:
举个例子,假设某个直接面向用户提供服务的B/S系统准备选择垃圾收集器,一般来说延迟时间是这类应用的主要关注点,那么:

  • 如果你有充足的预算但没有太多调优经验,那么一套带商业技术支持的专有硬件或者软件解决方案是不错的选择,Azul公司以前主推的Vega系统和现在主推的Zing VM是这方面的代表,这样你就可以使用传说中的C4收集器了。
  • 如果你虽然没有足够预算去使用商业解决方案,但能够掌控软硬件型号,使用较新的版本,同时又特别注重延迟,那ZGC很值得尝试。
  • 如果你对还处于实验状态的收集器的稳定性有所顾虑,或者应用必须运行在Win-dows操作系统下,那ZGC就无缘了,试试Shenandoah吧。
  • 如果你接手的是遗留系统,软硬件基础设施和JDK版本都比较落后,那就根据内存规模衡量一下,对于大概4GB到6GB以下的堆内存,CMS一般能处理得比较好,而对于更大的堆内存,可重点考察一下G1。
    当然,以上都是仅从理论出发的分析,实战中切不可纸上谈兵,根据系统实际情况去测试才是选择收集器的最终依据。
3.7.3 虚拟机及垃圾收集器日志

阅读分析虚拟机和垃圾收集器日志是处理Java虚拟机内存问题的必备基础技能;

3.7.4 垃圾收集器参数总结

3.8 实战: 内存分配与回收策略

3.9小结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值