深入理解Java虚拟机:Jvm总结-垃圾收集器与内存分配策略

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

3.1 意义

  • Java堆和方法区具有不确定性:一个接口的多个实现类、一个方法的不同条件分支需要的内存可能不一样。
  • 程序运行起来才知道到底会创建什么对象,创建多少个对象。
  • 动态分配内存和垃圾回收
  • 排查内存泄漏和内存溢出时
  • 当垃圾收集影响系统的并发量,需要手动调节

3.2 对象已死?

进行垃圾回收前,需要判断对象是否存活

3.2.1 引用计数算法

  • 在对象中设置一个引用计数器,被一个地方引用就加一,引用失效减一
  • 无法解决循环引用的问题

3.2.2 可达性分析算法

  • 创建GC Roots作为起始节点集,每个正常存活的对象根据引用关系一定能到达根对象(引用链),不可达则被回收。
    在这里插入图片描述

  • 固定可做为GC Roots的对象包括:

    • 虚拟机栈(局部变量表)中引用的对象,比如方法参数、局部变量、临时变量等
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象,比如字符串常量池里的引用
    • 本地方法栈中JNI(Java Native Interface)引用的对象
    • Java虚拟机内部的引用,比如基本数据类型对应的class对象,经常出现的异常对象(OOM),系统类加载器
    • 被同步锁(synchronized关键字)持有的对象
    • 反映Java虚拟机内部情况的JMX(一组规范)中MBean、JVMTI(一个构建java调试和分析工具的接口)中注册的回调、本地代码缓存等
    • 根据垃圾收集器和回收内存区域的不同,会有其他对象加入(比如只针对堆中某一块内存垃圾回收,这块内存里的对象可能被其他地方的对象引用,也需要把其他地方的对象加入RCRoots)

3.2.3 再谈引用

为了凸显一部分可以根据内存紧缺程度选择性回收的对象,对引用的概念进行扩充,分为强引用、软引用、弱引用和虚引用,引用强度依次减弱。

  • 强引用是最传统的“引用”,类似“Object obj = new Object()”。只要强引用关系存在,就不会被回收
  • 软引用关联的对象,会存活到抛出内存溢出异常前的一次垃圾回收。
  • 弱引用对象在下一次垃圾回收就会被回收
  • 虚引用用于跟踪对象被垃圾回收的状态,虚引用本身不决定对象的生命周期。

3.2.4 生存还是死亡?

(finalize()方法,不推荐使用)

一个对象的真正死亡,会经历两次标记过程:第一次标记在可达性分析算法判定不可达时,第二次标记为没有必要执行finalize()方法。finalize方法是Java中对象进行清理操作的最后一次机会。它在对象即将被垃圾收集器回收时调用,允许对象释放资源,例如关闭文件或网络连接。

但是运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐使用。使用try-finally或者其他方式都可以做得更好。

3.2.5 回收方法区

主要回收两部分:废弃常量和不使用的类

  • 废弃常量:没有任何对象引用该常量,且垃圾收集器判断有必要,就会回收
  • 不使用的类,符合以下三个条件将被允许进行回收。:
    • 该类所有的实例都被回收,即堆中不存在该类及其子类的实例
    • 加载该类的类加载器已被回收
    • 该类对应的java.lang.Class对象没有被引用,无法在任何地方通过反射访问该类的方法

3.3 垃圾收集算法

从如何判定对象消亡的角度,垃圾收集算法可以划分为“引用计数式垃圾收集”和“追踪式垃圾收集”两类。本节讲的算法都是追踪式垃圾收集,即追踪程序中的对象引用关系来确定可达性,回收不可达对象的内存。

3.3.1 分代收集理论

分代假说:

  1. 弱分代假说:绝大多数对象都是朝生夕灭的
  2. 强分代假说:熬过越多次垃圾收集的对象就越是难以消亡
  3. 跨代引用假说:跨代引用相对于同代引用只占少数

所以可以将Java堆根据对象的存活时间分区,如果一个区域大部分对象都是朝生夕灭的,回收时只需要关注其中少量存活的对象,然后把这些剩下存活的对象集中管理就可以减少时间开销并有效利用内存空间。所以一般会把堆分成两个部分,“新生代(Young/Nursery)”“老年代(Old/Tenured)”,新生代中垃圾回收后依旧存活的对象将会逐步晋升为老年代。

但是会存在跨代引用的情况,即新生代中对象有可能被老年代所引用,实际情况中,存在互相引用的对象是偏向于同时存活或同时消亡的(被老年代引用的对象在收集时会存活下来然后晋升到老年代),根据第三个假说,为了避免检查跨代引用时扫描整个老年代,可以建立一个记忆集,把老年代划分成若干小块并标识出哪块存在跨代引用,在发生新生代收集时,只有包含跨代引用的块会被加入到GC Roots扫描。

一些名词:

  • 部分收集(Partial GC):
    • 新生代收集(Minor GC/Young GC)
    • 老年代收集(Major GC/Old GC):目前只有CMS收集器会单独收集老年代,会产生和整堆收集的混淆。
    • 混合收集(Mixed GC):目前只有G1会有这种行为
  • 整堆收集(Full GC):收集整个Java堆和方法区

3.3.2 标记-清除算法

  • 分为标记和清除两个阶段,可以标记需要回收的对象,也可以标记不需要回收的对象。

  • 最基础的收集算法

  • 两个缺点:

    • 执行效率不稳定,随对象数量增长而降低

    • 清除后会产生大量不连续的内存碎片,会影响之后给大对象分配内存,导致提前触发另一次垃圾收集。

      在这里插入图片描述

3.3.3 标记-复制算法

  • 将内存按容量分为大小相等的两块,每次只使用一块,其中一块内存用完就把存活的对象复制到另一块,并清理这一块的所有内存。

  • 好处是不需要考虑内存碎片,只需要在另一块内存中移动指针按顺序分配。

  • 明显的缺点就是只能使用一半空间

  • 这种收集算法优先考虑回收新生代,但是新生代绝大部分对象活不过第一轮收集,所以可以优化分区策略,“Appel式回收”,分为一块较大的Eden空间和两块较小的Survivor空间,每次只使用Eden和一块Survivor空间,垃圾回收时将存活的对象放到另一块Survivor空间中。一般Eden和一块Survivor的比例是8:1

  • 分配担保:另一块Survivor不足以放入存活的对象时,将会直接放入老年代。

3.3.4 标记-整理算法

  • 标记过程与标记-清除算法一样,只不过让存活的对象都向一端移动,然后清理边界以外的内存。移动过程会出现“Stop The World”
    在这里插入图片描述

  • 在关注吞吐量时,可以基于标记整理算法,在关注延迟时,可以基于标记清楚算法。因为移动对象会在内存回收时变复杂,不移动对象会在内存分配时变复杂。

  • CMS收集器在平时会使用标记清除算法,但是当空间碎片影响到大对象的分配时,就会采用一次标记整理算法。

3.4 HotSpot算法细节实现

3.4.1 根节点枚举

  • 从GC Roots找引用链
  • Stop The World,因为如果对象引用关系还在不停变化,会影响收集的准确性
  • 虚拟机应当可以直接得到哪些地方记录了对象引用,比如OopMap,会记录栈和寄存器里引用的位置。

3.4.2 安全点

  • 安全点(Safepoint)是一个特定的位置或时间点,在这个点上,所有的线程都可以安全地暂停,以便 JVM 可以执行某些全局操作,例如垃圾收集
  • 设定安全点,要避免程序长时间执行而不进入安全点,可以设定在循环的开始或结束,方法调用入口或返回处,异常处理时
  • 两种方法到达安全点:
    • 抢先式中断:垃圾收集时,中断所有线程,如果有线程不在安全点就让他继续执行直到安全点再中断。几乎没有虚拟机用这种方法来相应GC
    • 主动式中断:不直接对线程操作,而是设定一个标志位,线程自己不断检查这个标志,标志为真时,主动暂停线程等待垃圾收集。标志位包括安全点,也包括需要分配内存时。

3.4.3 安全区域

  • 程序不执行时,是不会主动进入安全点的,所以引入安全区域(在这个区域内引用关系不会发生变化),在这个区域内任意地方都可以开始垃圾收集。
  • 可以在线程阻塞时使用
  • 实现原理
    • 线程执行安全区域里的代码时,会标记自己进入了安全区域,表示不会影响要被GC的对象
    • 离开时检查虚拟机是否完成了需要STW的操作,完成的话线程就继续执行否则一直等待

3.4.4 记忆集与卡表

  • 记忆集记录了从非收集区域指向收集区域指针的集合
  • 三种精度:
    • 字长精度:记录精确到机器字长(决定指针长度),该字包含跨代指针
    • 对象精度:精确到对象,该对象有字段包含跨代指针
    • 卡精度(卡表,也是最常用的精度):精确到一块内存区域,该区域有对象含有跨代指针
  • 卡表的形式可以是一个字节数组,数组中每一个元素对应一块内存(卡页),然后当这块内存存在跨代指针就把对应的元素值标为1,称为变脏。筛选变脏元素即可得到有跨代指针的内存块。

3.4.5 写屏障

  • 写屏障可以看做对引用字段赋值(有其他分代区域对象引用当前区域对象)时的AOP,这个AOP实现了把卡表数组元素变脏。
  • 产生Around通知,分为写前屏障和写后屏障,基本只用写后屏障来维护卡表状态。
  • 虽然只要对引用更新就会调用写屏障,但这种开销比Minor GC时扫描整个老年代小得多
  • “伪共享”问题:缓存系统以缓存行为单位存储,在多线程时,如果需要修改的卡表元素在同一缓存行,就会彼此影响。一个简单的方法就是不采用无条件的写屏障,增加一个判断语句。

3.4.6 并发的可达性分析

垃圾收集算法中都需要”标记“这一过程,在根节点枚举中已经找到了RC Roots,但是还需要根据RC Roots寻找链上的对象。但是寻找过程会根据堆的的增大而增加停顿时间,所以要尽量削减这部分时间。

  • 引入三色标记

    • 白色:表示对象尚未被垃圾收集器访问过。若分析结束后仍然是白色的对象,即代表不可达。

    • 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过

    • 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。表示安全存活。

    • 在这里插入图片描述

    • 同时满足以下两个条件就会把应该保留的对象清除,也就是误把黑色标位白色

      • 赋值器插入了一条或多条由黑到白的新引用
      • 赋值器删除了所有由灰色到该白色对象的直接或间接引用
  • 两种解决方案(只需要破坏一个条件,且都通过写屏障实现):

    • 增量更新:记录新插入的黑到白的引用,在并发扫描结束后以新插入引用的黑对象为根再扫描一遍
    • 原始快照:当灰色要删除到白色的引用时,将这个引用关系记录下来,并发扫描结束之后再将记录下来的引用关系的灰色为根再扫描一次。

★3.5 经典垃圾收集器

包含了从JDK7到JDK11之前所包含的全部可用的垃圾收集器,注意所处的区域和彼此的搭配。
在这里插入图片描述

3.5.1 Serial收集器(新生代)

  • 单线程,垃圾收集时,会Stop The World
    在这里插入图片描述

  • 简单高效,内存受限的环境里额外内存消耗最小

  • 没有线程交互开销,单线程收集效率最高

3.5.2 ParNew收集器(新生代)

  • Serial收集器的多线程版本,使用多条线程并行进行垃圾收集,其余行为与Serial一致
    在这里插入图片描述

  • 除了Serial,只有他能与CMS配合工作

  • 多核时垃圾收集效果比较好

3.5.3 Parallel Scavenge收集器(新生代)

  • 基于标记复制算法,也并行收集
  • 关注吞吐量的收集器,适合在后台运算不需要太多交互的任务
  • 两个参数用于精确控制吞吐量:
    • -XX:MaxGCPauseMillis:设置一个大于零的毫秒数,系统尽力使回收内存的时间不超过这个设置的时间
    • -XX:GCTimeRatio:参数的值是一个大于0小于100的整数,垃圾收集时间占总时间的比率。比如参数设置为19,允许的最大垃圾收集时间占总时间的1/(1+19)=5%。默认为99,即1%。
  • 还有一个参数:-XX:+UseAdaptiveSizePolicy,开关参数,开启后系统自适应调节新生代大小,Eden与Survivor区的比例,晋升老年代大小等细节参数。

3.5.4 Serial Old收集器(老年代)

  • 单线程收集器,使用标记整理算法。

在这里插入图片描述

  • 主要供客户端使用

3.5.5 Parallel Old收集器(老年代)

  • 多线程并发收集,基于标记整理算法
  • 与Parallel Scanvenge收集器配合使用,注重吞吐量

在这里插入图片描述

3.5.6 CMS收集器(老年代)

  • 以获取最短回收停顿时间为目标,提高用户交互体验
  • 基于标记清除算法
  • 四个步骤
    1. 初始标记(需要STW):标记与GC Roots直接关联的对象
    2. 并发标记(不需要STW):从与GC Roots直接关联的对象开始遍历整个对线图,与垃圾收集并发执行
    3. 重新标记(需要STW):修正并发标记期间产生的标记变动的对象的标记记录(使用增量更新)
    4. 并发清除(不需要STW):清理死亡的对象,不需要移动存活对象,可以与用户线程并发执行

在这里插入图片描述

  • 优点:并发收集,低停顿
  • 三个缺点:
    • 很吃处理器资源,处理器核心数量不足4个时,对用户线程影响比较大,应用程序速度会变慢。
    • 在并发标记和并发清除阶段用户线程还在运行,还会产生浮动垃圾,CMS在这一次清理过程无法清理,只能留给下一次。同样,还需要在垃圾收集时给用户线程分配内存,所以不能等到老年代满了再收集,需要预留内存给用户线程,如果预留内存不足以分配新的对象,会产生“并发失败”导致暂停用户线程并且临时将Serial Old启动来收集老年代。
    • 基于标记清除算法,会产生大量碎片,没有足够空间给大对象,提前触发Full GC。

3.5.7 Garbage First收集器(面向堆内存的任何部分)

  • 面向服务端,平衡吞吐量和延迟,可以由用户制定期望的停顿时间
  • 面向局部收集的设计思路,衡量标准由分代转到哪块内存中存放垃圾多
  • 将Java堆划分为多个大小相等的独立区域(region),每个Region可以有自己的新生代和老年代,不同Region有不同的策略来收集。
  • Humongous区域来存储超过Region二分之一的大对象,超级大对象由几个连续的Humongous区域来存储
  • 将Region作为单次回收的最小单元,并优先处理回收价值大的一些Region

在这里插入图片描述

  • G1的记忆集是哈希表,Key是 别的Region的起始地址,Value是一个集合存储卡表的索引号。
  • G1使用原始快照(STAB)处理并发标记阶段中用户线程出现改变对象引用关系的情况,使用了写前屏障跟踪指针变化情况。
  • 再并发回收过程中还会有新对象创建,G1使用两个指针(TAMS Top at Mark Start),把Region中的一部分空间划分出来用于创建新对象,且新对象必须在两个指针位置以上,默认把新对象认为是存活对象不回收。
  • G1通过记录回收耗时、每个Region记忆集里脏卡数量等可计算值,然后计算平均值等信息来决定哪些Region组成回收集。
  • 过程可分为四步:
    1. 初始标记(需要STW,但耗时很短):标记GC Roots直接关联的对象,修改TAMS的值。
    2. 并发标记(不需要STW):从标记的对象递归扫描整个堆里的对象图,扫描完以后再处理STAB记录的有变动的对象
    3. 最终标记(需要STW):处理并发结束后扔遗留的STAB记录
    4. 筛选回收(需要STW,涉及对象移动):更新Region统计数据,并根据回收价值排序,根据用户期望停顿时间自由选择任意多个Region构成回收集,把决定存活的对象复制到空的Region,在清理掉整个旧的Region空间。

在这里插入图片描述

  • 优点是不会产生内存碎片,因为整体看是标记整理算法,局部是标记复制算法,都不会产生。
  • 缺点是G1的卡表比较复杂,记忆集占用很多堆空间。且写屏障更复杂,不仅写后屏障就因为卡表复杂而更繁琐,还要用写前屏障实现SATB,导致耗费更多资源,以至于需要把写屏障异步处理。

3.6 低延迟垃圾收集器

硬件提高有助于吞吐量,但反而对延迟不利,延迟变成了垃圾收集器最重视的指标。CMS和G1之前的所有垃圾收集器都需要STW,CMS使用标记清除算法,难逃碎片堆积导致的STW出现,G1虽然停顿时间不会过长,但也会暂停。

3.6.1 Shenandoah收集器

  • Shenandoah不仅进行并发的垃圾标记,还并发进行对象清理后的整理。像是G1的继承者但有三点不同
    • 回收阶段与用户线程并发
    • 默认不使用分代收集
    • 记忆集改为“连接矩阵”,连接矩阵可以简单理解为一张二维表格,如果Region N有对象指向Region M,就在表格的N行M列中打上一个标记
  • 九个阶段:
    1. 初始标记(需要STW):标记与GC Roots直接关联的对象
    2. 并发标记(不需要STW):遍历对象图,标记出全部可达的对象
    3. 最终标记(需要STW):处理剩余的SATB,并统计回收价值高的Region,构成回收集
    4. 并发清理(不需要STW):清理整个区域没有存活对象的Region
    5. 并发回收(不需要STW):把存活对象复制一份到空Region中,通过读屏障和转发指针
    6. 初始引用更新(需要STW):建立一个集合点,来确保并发回收完成任务。
    7. 并发引用更新(不需要STW):把指向旧对象的引用更新到新地址。
    8. 最终引用更新(需要STW):修正存在于GC Roots中的引用
    9. 并发清理(不需要STW):经过以上步骤,回收集中的Region已无存活对象,直接回收这些Region。

在这里插入图片描述

  • Brooks Pointer: 转发指针,在原有对象布局的结构的最前面加一个新的引用,指向自己,当对象有了新副本,该指针指向新副本,将所有该对象的访问转发到新的副本上。

    会出现多线程竞争问题,所以要对转发指针的访问操作采取同步措施,让收集器线程或者用户线程对转发指针的访问只有其中之一能够成功,要覆盖全部对象的访问需要使用读屏障,读屏障性能开销太大,改为引用访问屏障,只拦截对引用的访问。

3.6.2 ZGC收集器

  • 基于Region的堆内存布局,但Region具有动态性:动态创建和销毁以及动态区域大小。x64中,Region分为三种:

    • 小型Region:容量固定为2MB,用于放置小于256KB的小对象。
    • 中型Region:容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。
    • 大型Region:容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象,不会被重分配。
  • 染色指针:将少量信息直接存储到指针上的技术。将64位指针的剩余46位提取4位存储四个标志信息。

    • 染色指针可以使得一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉
    • 染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,尤其是写屏障
  • GC的运作过程大致可划分为以下四个大的阶段。全部四个阶段都是可以并发执行的,仅是两个阶段中间会存在短暂的停顿小阶段

    在这里插入图片描述

3.7 选择合适的垃圾收集器

★3.7.1 Epsilon收集器

运行负载极小、没有任何回收行为。适合只要运行数分钟甚至数秒的应用,只要Java虚拟机能正确分配内存,在堆耗尽之前就会退出。

★3.7.2 收集器的权衡

需要考虑:

  • 应用程序的关注点:如果是数据分析、科学计算类的任务,目标是能尽快算出结果,那吞吐量就是主要关注点;如果是SLA应用,那停顿时间直接影响服务质量,严重的甚至会导致事务超时,这样延迟就是主要关注点;而如果是客户端应用或者嵌入式应用,那垃圾收集的内存占用则是不可忽视的
  • 运行应用的基础设施:系统架构,处理器数量,内存大小,操作系统
  • JDK的发行商和版本

3.7.3 虚拟机及垃圾收集日志

-Xlog[:[selector][:[output][:[decorators][:output-options]]]]

  • 由以上的指令控制,最重要的参数是选择器selector,由标签和日志级别共同组成。垃圾收集器的标签为“gc”,日志级别从低到高,共有Trace,Debug,Info,Warning,Error,Off六种级别,日志级别决定了输出信息的详细程度,默认级别为Info。

  • 还可以使用修饰器(Decorator)来要求每行日志输出都附加上额外的内容,如果不指定,默认值是uptime、level、tags这三个,支持附加在日志行上的信息包括:

    ·time:当前日期和时间。

    ·uptime:虚拟机启动到现在经过的时间,以秒为单位。

    ·timemillis:当前时间的毫秒数,相当于System.currentTimeMillis()的输出。

    ·uptimemillis:虚拟机启动到现在经过的毫秒数。

    ·timenanos:当前时间的纳秒数,相当于System.nanoTime()的输出。

    ·uptimenanos:虚拟机启动到现在经过的纳秒数。

    ·pid:进程ID。

    ·tid:线程ID。

    ·level:日志级别。

    ·tags:日志输出的标签集。

  • 几个例子:

    • 查看GC基本信息,在JDK 9之前使用-XX:+PrintGC,JDK 9后使用-Xlog:gc
    • 查看GC详细信息,在JDK 9之前使用-XX:+PrintGCDetails,在JDK 9之后使用-X-log:gc*
    • 查看GC前后的堆、方法区可用容量变化,在JDK 9之前使用-XX:+PrintHeapAtGC,JDK 9之后使用 -Xlog:gc+heap=debug
    • 查看GC过程中用户线程并发时间以及停顿的时间,在JDK 9之前使用-XX:+Print-GCApplicationConcurrentTime以及-XX:+PrintGCApplicationStoppedTime,JDK 9之后使用-Xlog:safepoint
    • 查看收集器Ergonomics机制(自动设置堆空间各分代区域大小、收集目标等内容,从Parallel收集器开始支持)自动调节的相关信息。在JDK 9之前使用-XX:+PrintAdaptive-SizePolicy,JDK 9之后使用-Xlog:gc+ergo*=trace
    • 查看熬过收集后剩余对象的年龄分布信息,在JDK 9前使用-XX:+PrintTenuring-Distribution,JDK 9之后使用-Xlog:gc+age=trace

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

对象内存分配基本都是在堆上分配,经典分代情况下,新生对象会分配在新生代,少部分大对象会直接分配到老年代。分配规则不固定,取决于虚拟机设定和垃圾收集器。主要关注分析方法

以下使用Serial加Serial Old测试

3.8.1 对象优先在Eden分配

大多数情况,对象在Eden区分配,Eden没有足够空间会发生一次Minor GC

3.8.2 大对象直接进入老年代

  • 大对象就是指需要大量连续内存空间的Java对象,典型的大对象是很长的字符串或者元素数量很庞大的数组
  • 避免大对象的原因:容易提前触发垃圾收集,复制时产生大量开销
  • HotSpot虚拟机提供了-XX:PretenureSizeThreshold参数(只对Serial和ParNew有效),指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区之间来回复制,产生大量的内存复制操作。

3.8.3 长期存活的对象进入老年代

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

3.8.4 动态对象年龄判定

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

3.8.5 空间分配担保

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

冒险:把Survivor无法容纳的对象直接送入老年代,前提是老年代有空间容纳这些对象,但不确定被容纳的对象其中有多少能在回收中存活下来。只能取历史平均值来赌概率,但可以避免Full GC过于频繁。

3.9 小结

垃圾收集器在许多场景中都是影响系统停顿时间和吞吐能力的重要因素之一,虚拟机之所以提供多种不同的收集器以及大量的调节参数,就是因为只有根据实际应用需求、实现方式选择最优的收集方式才能获取最好的性能。没有固定收集器、参数组合,没有最优的调优方法,虚拟机也就没有什么必然的内存回收行为。因此学习虚拟机内存知识,如果要到实践调优阶段,必须了解每个具体收集器的行为、优势劣势、调节参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/421010.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

javascript柯里化

return a b c d; } //通常调用方式 var sum add(1, 2, 3, 4); //柯里化的调用方式 var curryAdd Curry(add); var sum curryAdd(1)(2)(3)(4); //或者很多奇怪的方式调用 var sum curryAdd(1, 2)(3, 4); var sum curryAdd(1, 2, 3)(4); var sum curryAdd(1)(…

5.第二阶段x86游戏实战2-认识内存

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 工具下载: 链接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

24/9/6算法笔记 kaggle 房屋价格

预测模型主要分为两大类: 回归模型:当你的目标变量是连续的数值时,你会使用回归模型进行预测。回归模型试图找到输入特征和连续输出之间的关联。一些常见的回归模型包括: 线性回归(Linear Regression)岭回归…

FFmpeg 7.0 版本 “Dijkstra”的特点概述

FFmpeg 7.0 FFmpeg 官网:https://ffmpeg.org/FFmpeg 官网更新日志,2024.4.5 号发布代号"Dijkstra"的 7.0 版本的 FFmpeg,如下截图: 为什么叫 Dijkstra“Dijkstra” 指的是艾兹格戴克斯特拉(Edsger Wybe Dijkstra),他是一位荷兰计算机科学家,对计算机科学领域…

C#使用TCP-S7协议读写西门子PLC(一)

之前本人发布西门子S7协议的报文 西门子PLC的S7协议报文解析说明_西门子报文详解-CSDN博客 西门子PLC的S7协议是西门子公司在ModbusTcp协议的基础上自定义的一种协议,仅支持西门子PLC,S7协议本质仍然属于TCP协议的一种自定义具体实现 第一步,准备工作。VS2022中新建窗体应…

Redis学习Day3——项目工程开发

扩展阅读推荐: 黑马程序员Redis入门到实战教程_哔哩哔哩_bilibili 一、项目介绍及其初始化 学习Redis的过程,我们还将遇到各种实际问题,例如缓存击穿、雪崩、热Key等问题,只有在实际的项目实践中解决这些问题,才能更好…

教师节特辑:AI绘制的卡通人物,致敬最可爱的人‍

【编号:9】教师节到了,今天我要分享一组由AI绘制的教师节主题卡通人物插画,每一幅都充满了对老师的敬意和爱戴。让我们一起用这些可爱的卡通形象,向辛勤的园丁们致敬! 🎓【教师形象】 这…

域名证书,泛域名证书,sni

文章目录 前言一、证书1.全域名证书2.泛域名证书 二、域名证书的使用1、浏览器请求域名证书流程对全域名证书的请求流程对泛域名证书的请求流程ssl client-hello携带server name 报文 2、浏览器对证书的验证流程 三、域名证书和sni 前言 本文介绍了泛域名证书和全域名证书的区别…

JavaWeb【day12】--(SpringBootWeb登录认证)

案例-登录认证 在前面的课程中,我们已经实现了部门管理、员工管理的基本功能,但是大家会发现,我们并没有登录,就直接访问到了Tlias智能学习辅助系统的后台。 这是不安全的,所以我们今天的主题就是登录认证。 最终我们…

【32单片机篇】项目:智能台灯

一、项目需求 1. 红外传感器检测是否有人,有人的话实时检测距离,过近则报警;同时计时,超过固定时间则报警; 2. 按键 1 切换工作模式:智能模式、按键模式、远程模式; 3. 智能模式下,…

UML之类图详解

犬余🐶 “我欲贱而贵,愚而智,贫而富,可乎? 曰:其唯学乎” 零、什么是类图 类图(Class Diagram)是面向对象系统建模中最常用和最重要的图,它通过图形化的方式展示系统中的…

Conda安装R环境并在Jupyter Lab中运行

说明: Conda 安装 R 环境,并在 Jupyter Lab 中运行 R 脚本。 1. 创建环境并安装r-base 创建环境:conda create -n [环境名] 激活环境:conda activate [环境名] 安装 Jupyter Lab:conda install -c conda-forge jupy…

Promise详解、自定义

这里写目录标题 一、.Promise对象详解1. Promise是什么2. Promise的优点3. 异步编程4. then(onFulfilled, onRejected):5. util.promisify 方法:6. promise的状态改变7.Promise 对象的值8. Promise的基本执行流程9. Promise的API10. Promise中的关键问题…

怎么修复松下相机死机视频只有0字节(0KB)的MDT文件【实测可修复】

死机后视频文件大小仅为0字节 松下S5相机录像死机,关机重新开机后有一个视频文件变成MDT,大小为0KB,录了30多分钟,本应为MOV格式的视频。0字节文件可以修复吗?怎么修复0字节的MDT文件为视频? 数据提取与视…

四款免费视频剪辑工具使用感受与优劣势总结

在如今这个视频内容如火如荼的时代,如何快速高效地完成视频剪辑成为许多人关心的问题;今天,我们就来轻松愉快地聊一聊本人常用的四款免费的视频剪辑工具;这四款工具各有千秋,让我们一起来看看它们的使用感受和优劣势吧…

数据结构(并查集) How did you do it? 怎么做到的!!!

一、前言 并查集的历史 1964年, Bernard A. Galler 和 Michael J. Fischer 首次描述了不相交的并查集,1975 年,Robert Tarjan 是第一个证明O(ma(n))(逆阿克曼函数 (opens new window))算法时间复杂度的上限&#xff…

C语言深入了解指针一(14)

文章目录 前言一、内存和地址内存究竟该如何理解编址 二、指针变量和地址取地址操作符&解引用操作符*指针变量的大小 总结 前言 终于来到指针啦!如前篇末尾总结所说,这是你们马上要下大功夫的地方   但是,就像我们上初中的时候&#xf…

【开发工具】IntelliJ IDEA插件推荐:Json Helper——让JSON处理更高效

导语:在Java开发过程中,JSON作为一种轻量级的数据交换格式,被广泛应用于前后端数据交互。今天,我要为大家介绍一款IntelliJ IDEA插件——Json Helper,帮助开发者更高效地处理JSON数据。 一、什么是Json Helper&#x…

智能优化算法-樽海鞘优化算法(SSA)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 樽海鞘优化算法 (Salp Swarm Algorithm, SSA) 虽然名称中提到的是“樽海鞘”,但实际上这个算法是基于群体智能的一种元启发式优化算法,它模拟了樽海鞘(Salps)在海…

C语言:刷题日志(3)

一.猴子选大王 一群猴子要选新猴王。新猴王的选择方法是:让N只候选猴子围成一圈,从某位置起顺序编号为1~N号。从第1号开始报数,每轮从1报到3,凡报到3的猴子即退出圈子,接着又从紧邻的下一只猴子开始同样的报数。如此不…