垃圾回收
判断对象存活一般有两种方式
1、引用计数算法
2、可达性分析算法:JVM 使用
引用计数算法(Reference Counting)
1、对每个对象保存一个整型的引用计数器属性,用于记录对象被引用情况
(1)对于一个对象 A,只要有任何一个对象引用 A,则 A 的引用计数器就加 1;当引用失效时,引用计数器就减 1
(2)只要对象 A 的引用计数器的值为 0,即表示对象 A 不可能再被使用,可进行回收
2、优点
(1)实现简单,垃圾对象便于辨识
(2)判定效率高,回收没有延迟性
3、缺点
(1)需要单独的字段存储计数器,增加存储空间的开销
(2)每次赋值都需要更新计数器,伴随着加法和减法操作,增加时间开销
(3)无法处理循环引用的情况,在 Java 的垃圾回收器中没有使用这类算法
4、循环引用:当 p 的指针断开时,内部的引用形成一个循环
可达性分析算法(根搜索算法、追踪性垃圾收集)
1、优点
(1)实现简单、执行高效
(2)有效地解决循环引用的问题,防止内存泄漏
2、Java、C# 选择可达性分析算法
3、基本思路
(1)内存中的存活对象,都会被根对象集合直接或间接连接
(2)以根对象集合(GC Roots)为起始点,按照从上至下的方式,搜索被根对象集合所连接的目标对象是否可达
(3)根集合(GC Roots):一组必须活跃的引用
(4)引用链(Reference Chain):搜索所走过的路径
(5)如果目标对象没有任何引用链相连,则是不可达,表示该对象己经死亡,可以标记为垃圾对象
(6)只有能够被根对象集合,直接或间接连接的对象为存活对象
在 Java 中 GC Roots 的元素
1、固定 GC Roots 集合
(1)虚拟机栈(栈帧中的本地变量表)中引用的对象,如:各个线程被调用的方法中使用的参数、局部变量等
(2)本地方法栈内 JNI(native 方法)引用的对象
(3)方法区中类静态属性引用的对象
(4)方法区中常量引用的对象,如:字符串常量池中的引用
(5)所有被同步锁 synchronized 持有的对象
(6)JVM 内部的引用:基本数据类型对应的 Class 对象,一些常驻的异常对象(如:NullPointerException、OutOfMemoryError),系统类加载器
(7)反映 JVM 内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等
2、根据用户所选用的垃圾收集器,以及当前回收的内存区域不同,可以有其他临时性对象,共同构成完整 GC Roots 集合
(1)如:分代收集、局部回收(Partial GC)
(2)如果只针对 Java 堆中的某一块区域进行垃圾回收,该区域的对象有可能被其他区域的对象所引用,需要考虑将关联区域的对象,加入 GC Roots 集合中,才能保证可达性分析的准确性
3、判断技巧:因为 Root 采用栈方式存放变量和指针,所以如果一个指针保存堆中的对象,但是自身不存放在堆中,则其为一个 Root
引用
1、描述一类对象:当内存空间还足够时,则能保留在内存中;如果在进行垃圾收集后,内存空间仍不足,则可以抛弃这些对象
2、在 JDK 1.2 版之后,Java 对引用的概念进行了扩充
(1)4 种引用强度逐渐减弱:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)
(2)除强引用外,其他 3 种引用均在 java.lang.ref 包中
(3)Reference 子类中,只有终结器引用是包内可见的,其他 3 种引用类型均为 public,可以在应用程序中直接使用
(4)强引用(StrongReference):在程序代码之中,普遍存在的引用赋值,即类似“Object obj = new Object()”引用关系,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象
(5)软引用(SoftReference):在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中,进行第二次回收,如果这次回收后还没有足够的内存,才会抛出内存流出异常,可以配合引用队列来释放软引用自身
(6)弱引用(WeakReference):被弱引用关联的对象,只能生存到下一次垃圾收集之前,当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象,可以配合引用队列来释放弱引用自身
(7)虚引用(PhantomReference):一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例,为一个对象设置虚引用关联的唯一目的,就是能在这个对象被收集器回收时,收到一个系统通知,必须配合引用队列使用;主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 ReferenceHandler(守护线程)调用虚引用相关方法释放直接内存
(8)终结器引用(FinalReference):无需手动编码,必须配合引用队列使用,第一次 GC 时,终结器引用入队,被引用对象暂时没有被回收,再由 Finalizer 线程(低优先级),通过终结器引用,查找被引用对象,并调用其 finalize 方法,第二次 GC 时,才能回收被引用对象
标记-清除算法(Mark-Sweep)
1、非常基础和常见的垃圾收集算法
2、执行过程
(1)当堆中的有效内存空间(available memory)被耗尽时,就会停止整个程序(Stop The World),然后进行两项工作,第一项则是标记,第二项则是清除
(2)标记:Collector 从引用根节点开始遍历,标记所有被引用的对象,一般是在对象的 Header 中记录为可达对象
(3)清除:Collector 对堆内存从头到尾进行线性的遍历,如果发现某个对象在其 Header 中没有标记为可达对象,则将其回收
3、缺点
(1)效率不高
(2)在进行 GC 的时候,需要停止整个应用程序
(3)清理的空闲内存是不连续的,产生内存碎片,需要维护一个空闲列表
4、清除
(1)不是真的置空,而是把需要清除的对象地址,保存在空闲的地址列表中
(2)下次有新对象需要加载时,判断垃圾的位置空间是否足够,如果足够,就存放覆盖原有的地址
1、解决标记-清除算法在垃圾收集效率方面的缺陷
2、核心思想
(1)将活着的内存空间分为两块,每次只使用其中一块
(2)在垃圾回收时,将正在使用的内存中存活对象,复制到未被使用的内存块中
(3)之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收
3、优点
(1)没有标记和清除过程,实现简单,运行高效
(2)复制过去以后保证空间的连续性,不会出现内存碎片
4、缺点
(1)需要两倍的内存空间
(2)对于 G1 分拆内存为大量 region 的 GC,使用复制算法,GC 需要维护 region 之间对象引用关系,内存占用、时间开销较大
(3)如果系统中的垃圾对象较少,复制算法需要复制的存活对象数量较多,GC 效率低
5、应用场景
(1)要求:垃圾对象多,所需复制的存活对象相对较少
(2)新生代:一次 GC 通常可以回收 70% - 99% 内存空间,回收效率高
标记-压缩(标记-整理、Mark-Compact)算法
1、复制算法的高效,建立在存活对象少、垃圾对象多的情况下,但是在老年代,大部分对象都是存活对象
2、标记-清除算法应用在老年代,但执行效率低下,而且在执行完内存回收后,还会产生内存碎片
3、执行过程
(1)第一阶段:和标记清除算法相同,从根节点开始标记所有被引用对象
(2)第二阶段:将所有的存活对象压缩到内存的一端,按顺序排放
(3)清理边界外所有的空间
4、标记-压缩算法、标记-清除算法
(1)标记-压缩算法的最终效果,等价于标记-清除算法执行完成后,再进行一次内存碎片整理
(2)本质差异:标记-清除算法是一种非移动式,标记-压缩是移动式
5、需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可,比维护一个空闲列表的开销少
指针碰撞(Bump the Pointer)
1、如果内存空间以规整和有序的方式分布,即已用和未用的内存都各自一边,彼此之间维系着一个记录下一次分配起始点的标记指针,当为新对象分配内存时,只需要通过修改指针的偏移量将新对象分配在第一个空闲内存位置上
2、优点
(1)消除标记-清除算法当中,内存区域分散的缺点,需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可
(2)消除复制算法当中,内存减半的代价
3、缺点
(1)从效率上,标记-整理算法要低于复制算法
(2)移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址
(3)移动过程中,需要全程暂停用户应用程序,即 STW
分代收集算法
1、基于:不同的对象,拥有不同的生命周期
(1)不同生命周期的对象,采取不同的收集方式,提高回收效率
(2)一般把 Java 堆分为新生代、老年代,根据各个年代的特点使用不同的回收算法,以提高垃圾回收的效率
(3)生命周期长:与业务直接挂钩,如:Http 请求中的 Session 对象、线程、Socket 连接
(4)生命周期较短:程序运行过程中生成的临时变量,如:系统会产生大量 String 对象,由于其不变性,有些只用一次即可回收
2、目前几乎所有 GC 都采用分代收集算法
3、年轻代
(1)特点:区域相对老年代较小,对象生命周期短、存活率低、回收频繁
(2)复制算法速度最快,效率只和当前存活对象大小有关
(3)通过 HotSpot 中的两个 survivor 设计,缓解复制算法内存利用率不高的问题
4、老年代
(1)特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁
(2)由标记-清除,或标记-清除、标记-整理混合实现
(1)标记阶段的开销,与存活对象的数量成正比
(2)交换阶段的开销,与所管理区域的大小成正比
(3)压缩阶段的开销,与存活对象的数据成正比
5、以 HotSpot 中的 CMS 回收器为例
(1)CMS 基于 Mark-Sweep 实现的,对于对象的回收效率很高
(2)对于碎片问题,CMS 采用基于 Mark-Compact 算法的 Serial Old 回收器,作为补偿措施:当内存回收不佳(碎片导致的 Concurrent Mode Failure 时),将采用 Serial Old 执行 Full GC 以达到对老年代内存的整理
6、一般流程
(1)新创建的对象首先分配在 eden 区
(2)新生代空间不足时,触发 minor gc,eden 区、from 区存活的对象使用 copy,复制到 to 区中,存活的对象年龄 + 1,然后 survivor0、survivor1 交换 from、to 身份
(3)minor gc 会引发 stop the world,暂停其他线程,等垃圾回收结束后,恢复用户线程运行
(4)当幸存区对象的寿命超过阈值时,会晋升到老年代,最大的寿命是 15(4 bit)
(5)当老年代空间不足时,会先触发 minor gc,如果空间仍然不足,那么就触发 full fc ,停止的时间更长
相关 JVM 参数
含义 | 参数 |
堆初始大小 | -Xms |
堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
新生代大小 | -Xmn(最大、最小同时指定)或 -XX:NewSize=size(最小)、-XX:MaxNewSize=size(最大) |
幸存区比例(动态) | -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
幸存区比例 | -XX:SurvivorRatio=ratio(默认值 8) |
晋升阈值 | -XX:MaxTenuringThreshold=threshold |
晋升详情 | -XX:+PrintTenuringDistribution |
GC详情 | -XX:+PrintGCDetails -verbose:gc |
FullGC 前 MinorGC | -XX:+ScavengeBeforeFullGC |
垃圾回收器
1、串行
(1)单线程
(2)堆内存较小,适合个人电脑
(3)-XX:+UseSerialGC:开启 Serial(复制算法)、Serial Old(标记-整理算法)
(4)-XX:+UseParNewGC:开启 ParNew(Serial 多线程版),表示年轻代使用并行收集器,不影响老年代
2、吞吐量优先
(1)多线程
(2)堆内存较大,多核 cpu
(3)单位时间内,STW 时间最短,垃圾回收时间占比最低
(4)-XX:+UseParallelGC:开启 Parallel(复制算法)
(5)-XX:+UseParallelOldGC:开启 Parallel Old(标记-整理算法)
(6)-XX:+UseAdaptiveSizePolicy:开启自适应调节策略,年轻代的大小、Eden 和 Survivor 的比例、晋升老年代的对象年龄等参数,自动调整在堆大小、吞吐量、停顿时间之间的平衡点
(7)-XX:GCTimeRatio=ratio:垃圾收集时间占总时间的比例,1 / (ratio + 1),ratio (0, 100),默认值 99
(8)-XX:MaxGCPauseMillis=ms:设置垃圾收集器最大停顿时间(即 STW 的时间),单位为毫秒
(9)-XX:ParallelGCThreads=n:设置年轻代并行收集器的线程数,默认开启和 CPU 数据相同的线程数
3、响应时间优先
(1)多线程
(2)堆内存较大,多核 cpu
(3)单次 STW 的时间最短
(4)-XX:+UseConcMarkSweepGC:开启 CMS(标记-清除算法),开启该参数后,自动打开 -XX:+UseParNewGC,即:ParNew(新生代使用)+ CMS(老年代使用)+ Serial Old(CMS 并发失败的补救措施)
(5)-XX:ParallelGCThreads=n:设置年轻代并行收集器的线程数,默认开启和 CPU 数据相同的线程数
(6)-XX:ConcGCThreads=threads:设置并发标记的线程数,将 n 设置为并行垃圾回收线程数(ParallelGCThreads)的 1 / 4 左右
(7)-XX:CMSInitiatingOccupancyFraction=percent:默认值 60,堆空间到达指定 percent,则进行 GC,1 - percent 表示预留给浮动垃圾的空间
(8)-XX:+CMSScavengeBeforeRemark:在重新标记之前,进行新生代垃圾回收,重新标记阶段
(9)初始标记(Initial-Mark)阶段:程序中所有的工作线程都因为 Stop-the-World 机制而出现短暂暂停,只标记出 GC Roots 直接关联到对象,一旦标记完成就会恢复之前被暂停的所有应用线程,由于直接关联对象比较小,所以这里的速度非常快
(10)并发标记(Concurrent-Mark)阶段:从 GC Roots 直接关联对象开始,遍历整个对象图,这个过程耗时较长,但不需要停顿用户线程,可以与垃圾收集线程一起并发运行
(11)重新标记(Remark)阶段:为了修正并发标记期间,因为用户程序继续运作,而导致标记产生变动的、某一部分对象的标记记录,这个阶段仍是 STW,停顿时间通常比初始标记阶段稍长,但远比并发标记阶段的时间短;可能有新生代对象引用老年代对象,需要扫描整堆,但进行可达性分析,而新生代多数为垃圾,造成无用查找
(12)并发清除(Concurrent-Sweep)阶段:清理在(3)中判断已死亡的对象,释放内存空间,由于不需要移动存活对象,所以这个阶段可以与用户线程同时并发,可能产生新垃圾(浮动垃圾),需要在下次 GC 清理
(13)因为 CMS 为标记-清除算法,可能产生较多内存碎片,分配对象时,内存空间不足,CMS 退化为 Serial Old,串行 + 标记-整理算法,STW 时间较长
G1
1、Garbage First
(1)JDK 7u4 官方支持
(2)JDK 9 默认
2、应用场景
(1)同时注重吞吐量(Throughput)、低延迟(Low latency),默认的暂停目标是 200 ms
(2)超大堆内存,将堆划分为多个大小相等 Region
(3)整体:标记-整理算法,两个区域之间:复制 算法
3、相关 JVM 参数
(1)-XX:+UseG1GC:开启 G1
(2)-XX:G1HeapRegionSize=size:设置 Region 大小
(3)-XX:MaxGCPauseMillis=time:指定最大暂停时间
G1 回收过程
1、年轻代 GC
(1)JVM 启动时,G1 先准备好 Eden 区,程序在运行过程中,不断创建对象到 Eden 区,当 Eden 空间耗尽时,G1 会启动一次年轻代垃圾回收过程
(2)年轻代垃圾回收只会回收 Eden 区和 Survivor 区
(3)首先 G1 停止应用程序的执行(Stop-The-World),G1 创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集,包含年轻代 Eden 区和 Survivor 区所有的内存分段
(4)第一阶段:扫描根,根是指 static 变量指向的对象,正在执行的方法调用链条上的局部变量等,根引用连同 Remembered Set 记录的外部引用作为扫描存活对象的入口
(5)第二阶段:更新 Remembered Set,处理 Dirty Card Queue 中的 Card,更新 Remembered Set,此阶段完成后,Remembered Set 可以准确反映老年代对所在的内存分段中对象的引用
(6)第三阶段:处理 Remembered Set,识别被老年代对象指向 Eden 中的对象,这些被指向的 Eden 中的对象被认为是存活的对象
(7)第四阶段:复制对象,遍历对象树,Eden 区内存段中存活的对象,被复制到 Survivor 区中空的内存分段(to 区),Survivor 区内存段中存活的对象(from 区),如果年龄未达阈值,年龄会加 1,达到阀值会被会被复制到 Old 区中空的内存分段,如果 Survivor 空间不够,Eden 空间的部分数据会直接晋升到老年代空间
(8)第五阶段:处理引用,处理 Soft,Weak,Phantom,Final,JNI Weak 等引用,最终 Eden 空间的数据为空,GC 停止工作,而目标内存中的对象都是连续存储的,没有碎片,复制过程可以达到内存整理的效果,减少碎片
(9)Dirty Card Queue(脏卡队列):对于应用程序的引用赋值语句 Object.field = Object,JVM 会在之前、之后执行特殊的操作,以在 Dirty Card Queue 中入队一个保存对象引用信息的 Card,在年轻代回收时,GC 处理 Dirty Card Queue 中所有 Card,以更新 Remembered Set,保证 Remembered Set 实时准确的反映引用关系
(10)不在引用赋值语句处直接更新 Remembered Set,是为了性能需要,处理 Remembered Set 需要线程同步,开销会很大,使用队列性能更好
2、年轻代 GC + 并发标记
(1)在 Young GC 时会进行 GC Root 的初始标记,老年代占用堆空间比例达到阈值时,进行并发标记,由 -XX:InitiatingHeapOccupancyPercent=percent(默认 45%)决定
(2)初始标记阶段:标记从根节点直接可达的对象,该阶段 STW,并且会触发一次年轻代 GC
(3)根区域扫描(Root Region Scanning):G1 GC 扫描 Survivor 区直接可达的老年代区域对象,并标记被引用的对象,这一过程必须在 Young GC 之前完成
(4)并发标记(Concurrent Marking):在整个堆中进行并发标记(和应用程序并发执行),此过程可能被 Young GC 中断,若发现区域对象中的所有对象都是垃圾,则该区域会被立即回收,同时,会计算每个区域的对象活性(区域中存活对象的比例);进行 Pre-Write Barrier(写屏障),只要对象引用更改,则该对象进入 satb_mark_queue
(5)再次标记(Remark):由于应用程序持续进行,需要修正上一次的标记结果,检查并处理 satb_mark_queue,此阶段 STW,G1 采用比 CMS 更快的初始快照算法:Snapshot-At-The-Beginning(SATB)
(6)独占清理(Cleanup):计算各个区域的存活对象和 GC 回收比例,并进行排序,识别可以混合回收的区域,为下阶段做铺垫,此阶段 STW,并不会实际上去做垃圾的收集
(7)并发清理阶段:识别并清理完全空闲的区域
3、混合回收
(1)当越来越多的对象晋升到老年代时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即 Mixed GC,该算法并不是一个 Old GC,除了回收整个 Young Region,还会回收一部分 Old Region
(2)注意:是回收一部分老年代,而不是全部老年代,可以选择 Old Region 进行收集,从而控制垃圾回收的耗时,同时,Mixed GC 并不是 Full GC
(3)并发标记结束以后,老年代中回收绝对为垃圾的内存分段,计算出部分为垃圾的内存分段
(4)默认情况下,老年代的内存分段,会被分 8 次被回收(通过 -XX:G1MixedGCCountTarget 设置),但混合回收并不一定要进行 8 次
(5)混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden 区内存分段,Survivor 区内存分段
(6)混合回收的算法同样使用复制算法,只是包括回收老年代的内存分段,
(7)因为老年代中的内存分段默认分 8 次回收,G1 会优先回收垃圾多的内存分段,垃圾占内存分段比例越高的,越会被先回收
(8)过程:最终标记(Remark、STW) -> 拷贝存活(Evacuation、STW)
(9)-XX:G1MixedGCLiveThresholdPercent:决定内存分段是否被回收,默认为 65%,垃圾占内存分段比例要达到 65% 才会被回收,如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间
(10)-XX:G1HeapWastePercent:默认值为 10%,允许整个堆内存中有 10% 空间被浪费,如果发现可以回收的垃圾占堆内存的比例低于 10%,则不再进行混合回收,因为 GC 花费较多时间,但回收内存却很少
(11)-XX:MaxGCPauseMillis=ms:最大暂停时间,默认 200ms
4、可选过程:Full GC
(1)G1 目的:避免 Full GC 出现
(2)如果上述方式不能正常工作,G1 会停止应用程序的执行(Stop-The-World),退化为 Serial GC ,串行 + 标记-整理算法,STW 时间长
(3)导致 G1 Full GC 两个原因:回收阶段(Evacuation)时,没有足够的 to-space 来存放晋升的对象;并发处理过程完成之前空间耗尽,即新垃圾占用空间 > 回收空间
新生代回收的跨代引用(老年代引用新生代)问题
1、卡表
(1)Card Table
(2)若 Region 中某个老年代对象,引用新生代对象,则为脏卡
2、Remembered Set(记忆集)
(1)一个对象被不同区域引用的问题:一个 Region 不可能是孤立的,一个 Region 中的对象可能被其他任意 Region 中对象引用,判断对象存活时,需要扫描整个 Java 堆才能保证准确,会降低 MinorGC 的效率
(2)无论 G1 还是其他分代收集器,JVM 都是使用 Remembered Set 来避免全局扫描
(3)每个 Region 都有一个对应的 Remembered Set
(4)每次 Reference 类型数据写操作时,都会产生一个 Post-Write Barrier(写屏障)暂时中断操作,每次引用变更时,都进行此异步操作,标记为脏卡
(5)然后检查将要写入的引用指向的对象,是否和该 Reference 类型数据在不同的 Region(其他收集器:检查老年代对象是否引用新生代对象)
(6)如果不同,通过 Card Table 把相关引用信息,记录到引用指向对象的所在 Region,对应的 Remembered Set 中
(7)当进行垃圾收集时,在 GC 根节点的枚举范围加入 Remembered Set,保证不进行全局扫描,也不会有遗漏
JDK 8u20 字符串去重
1、优点:节省大量内存
2、缺点:略微增加占用 CPU 时间、新生代回收时间
3、-XX:+UseStringDeduplication
(1)将所有新分配的字符串放入一个队列
(2)当新生代回收时,G1 并发检查是否有字符串重复
(3)如果它们值相同,让它们引用同一个 char[]
4、注意,与 String.intern() 不同
(1)String.intern() 关注字符串对象
(2)字符串去重关注 char[]
(3)在 JVM 内部,使用不同的字符串表
JDK 8u40 并发标记类卸载
1、所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类
2、-XX:+ClassUnloadingWithConcurrentMark:默认启用
JDK 8u60 回收巨型对象
1、巨型对象:一个对象大于 Region 一半
2、G1 不会对巨型对象进行拷贝
3、回收时被优先考虑
4、G1 会跟踪老年代所有 incoming 引用,老年代 incoming 引用为 0 的巨型对象,就可以在新生代垃圾回收时处理掉
JDK 9 并发标记起始时间的调整
1、并发标记必须在堆空间占满前完成,否则退化为 Full GC
2、JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent
3、JDK 9 可以动态调整
(1)-XX:InitiatingHeapOccupancyPercent:设置初始值
(2)进行数据采样并动态调整
(3)总会添加一个安全的空档空间
新生代调优
1、特点
(1)所有 new 操作的内存分配非常廉价,TLAB(thread-local allocation buffer)
(2)死亡对象的回收代价是零
(3)大部分对象用过即死
(4)Minor GC 时间远远低于 Full GC
2、建议大小
(1)如果新生代太小,则会执行很多 Minor GC
(2)如果新生代太大,则只有执行 Full GC,这可能需要很长时间才能完成
(3)Oracle 建议将年轻代的大小,保持在大于 25%,且小于总堆大小的 50%
3、新生代能容纳所有 [并发量 * (请求 - 响应)] 数据
4、幸存区能保留 [当前活跃对象 + 需要晋升对象]
5、晋升阈值配置得当,让长时间存活对象尽快晋升
(1)-XX:MaxTenuringThreshold=threshold:晋升老年代年龄阈值
(2)-XX:+PrintTenuringDistribution:JVM 在每次新生代 GC 时,打印出幸存区中对象的年龄分布
老年代调优(以 CMS 为例)
1、CMS 老年代内存越大越好
2、先尝试不做调优,如果没有 Full GC,则说明老年代空间充裕,否则优先调优新生代
3、观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
(1)-XX:CMSInitiatingOccupancyFraction=percent
例
1、Full GC、Minor GC频繁
(1)说明空间紧张
(2)首先解决新生代空间紧张
(3)幸存区空间紧张,晋升阈值降低,生存周期短的对象转移到老年代
2、请求高峰期发生 Full GC,单次暂停时间特别长(CMS)
(1)存在新生代对象,引用老年代对象,导致重新标记扫面整堆,STW 扫描时间长
(2)-XX:+CMSScavengeBeforeRemark:在重新标记之前,进行新生代垃圾回收,重新标记阶段
3、老年代充裕情况下,发生 Full GC (CMS JDK 1.7)
(1)JDK 1.7 永久代属于 JVM 内存结构
(2)永久代内存空间不足,引起 Full GC
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战