文章目录[隐藏]
垃圾回收
1. 如何判断对象可以回收
1.1 引用计数法
1.1.1 定义
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的
1.1.2 弊端
循环引用,A对象引用B对象,B对象引用计数+1,B对象引用A,A对象引用计数+1。当没有谁再引用他们,他们不能被垃圾回收,因为引用计数没有归零。python在早期垃圾回收用的引用计数法。
1.2 可达性分析算法
1.2.1 定义
- java虚拟机中的垃圾回收器采用的是可达性分析算法(Reachability Analysis)
- 扫描堆中的对象,看是否能够沿着GC Root(根对象) 为起点的引用链找到该对象,找不到就可以进行垃圾回收
1.2.2 GC Root对象分类
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量(一般指被static修饰的对象,加载类的时候就加载到内存中)
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
- 所有被同步锁(synchronized关键字)持有的对象。
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
1.3 五种引用
1.3.1 强引用(Strong Reference)
- 无论任何情况下,即使系统内存不足,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
1.3.2 软引用(Soft Reference)
- 软引用是用来描述一些还有用,但非必须的对象。
- 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
- 可以配合引用队列来释放软引用自身
1.3.3 弱引用(Weak Reference)
- 弱引用也是用来描述那些非必须对象
- 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
- 可以配合引用队列来释放弱引用自身
1.3.4 虚引用(Phantom Reference)
- 虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。
- 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队, 由 Reference Handler 线程调用虚引用相关方法释放直接内存
1.3.5 终结器引用(Final Reference)
- 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象
1.3.6 示例代码
/**
* 演示软引用,配合引用队列
* -Xmx20m
*
* @author Enndfp
*/
public class Demo2 {
private static final int _4MB = 4 * 1024 * 1024;
public static void main(String[] args) {
List<SoftReference<byte[]>> list = new ArrayList<>();
// 引用队列
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for (int i = 0; i < 5; i++) {
// 关联了引用队列,当软引用所关联的 byte[] 被回收时,软引用自己会加到 queue 中
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
// 从队列中获取无用的软引用对象,并移除
Reference<? extends byte[]> poll = queue.poll();
while (poll != null) {
list.remove(poll);
poll = queue.poll();
}
System.out.println("===========================");
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
}
弱引用的使用和软引用类似,只是将 SoftReference 换为了 WeakReference
2. 垃圾回收算法
2.1 标记清除算法(Mark-Sweep)
2.1.1 定义
首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
2.1.2 优点
- 速度快
2.1.3 缺点
-
执行效率不稳定(如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低)
-
内存空间的碎片化问题(标记、清除之后会产生大 量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找 到足够的连续内存而不得不提前触发另一次垃圾收集动作)
2.2 标记整理算法(Mark Compact)
2.2.1 定义
在执行垃圾回收时,先标记完引用的对象,然后清除没有被引用的对象,最后整理剩余的空间,避免因内存碎片导致的问题。
2.2.2 优点
- 避免了内存碎片问题
2.2.3 缺点
- 效率低,速度慢(因为整理内存是为了避免存在内存碎片问题,所以整理需要消耗一定的时间,导致效率较低)
- 时间换空间
2.3 标记复制算法(Mark Copy)
2.3.1 定义
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
2.3.2 优点
- 避免了内存碎片问题
2.3.3 缺点
- 将可用内存空间缩小为原来一半
- 空间换时间
3.分代回收
- 对象首先分配在伊甸园区域
- 当伊甸园空间不足时,就会触发
Minor GC
,伊甸园和幸存区From
存活的对象使用标记复制算法移动到幸存区To
中,存活的对象年龄加1
,并且交换From
和To
- 移动存活对象(
Minor GC
、Major GC
、Full GC
等)都会触发“Stop The World
”(需要暂停用户线程来标记、清理可回收对象的,只是停顿时间相对而言要来的短而已,等垃圾回收结束,用户线程恢复运行) - 当对象年龄超过阈值时,会晋升至老年代,最大年龄为15(4bit)
3.1 垃圾回收触发条件
1.触发新生代垃圾回收(Minor GC)的条件:
- 当新生代中的伊甸园(Eden)空间满时,会触发Minor GC。伊甸园空间用于分配新创建的对象。
- 在Minor GC期间,存活的对象会被移动到幸存者(Survivor)空间中。幸存者空间也会被使用和填满,但大部分的对象通常会在Minor GC后晋升到老年代。
- Minor GC的主要目标是清理伊甸园和幸存者空间中的垃圾对象,将存活对象晋升到老年代。
2.触发老年代垃圾回收(Major GC或Full GC)的条件:
- 当老年代空间快要满时,会触发Major GC。老年代空间用于存放生命周期较长的对象。
- 如果执行Minor GC后,存活的对象在老年代中无法找到足够的空间存放,就会触发Major GC。
- 在使用CMS(Concurrent Mark-Sweep)垃圾回收器时,如果CMS的并发收集阶段无法跟上应用程序的垃圾产生速度,就可能会触发Full GC来进行老年代的回收。
- 在使用G1(Garbage-First)垃圾回收器时,如果在进行Mixed GC(混合收集)时,无法为存活的对象分配所需的内存空间,就会触发Full GC。
3.2 相关VM参数
含义 | 参数 |
---|---|
堆初始大小 | -Xms |
堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
新生代大小 | -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) |
幸存区比例(动态) | -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
幸存区比例 | -XX:SurvivorRatio=ratio |
晋升阈值 | -XX:MaxTenuringThreshold=threshold |
晋升阈值 | -XX:+PrintTenuringDistribution |
GC详情 | -XX:+PrintGCDetails -verbose:gc |
FullGC 前 MinorGC | -XX:+ScavengeBeforeFullGC |
4. 垃圾回收器
4.1 相关概念
- 并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。
- 并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾 收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于 垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。
- 吞吐量(Throughput):就是处理器用于运行用户代码的时间与处理器总消耗时间的比值
如果虚拟机完成某个任务,用户代码加上垃圾收集总共耗费了100分钟,其中垃圾收集花掉1分 钟,那吞吐量就是99%。
4.2 串行回收器
-XX:+UseSerialGC = Serial + SerialOld
4.2.1 特点
- 单线程
- 内存较小
- 安全点:让其他线程都在这个点停下来,以免垃圾回收时移动对象地址,使得其他线程找不到被移动的对象
- 因为是串行的,所以只有一个垃圾回收线程。且在该线程执行回收工作时,其他线程进入阻塞状态。
4.2.2 Serial收集器
Serial收集器是最基础、历史最悠久的收集器
特点:单线程、简单高效(与其他收集器的单线程相比),采用复制算法。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束(Stop The World)
4.2.3 ParNew 收集器
ParNew收集器其实就是Serial收集器的多线程版本。
特点:多线程、ParNew收集器默认开启的收集线程数与CPU的数量相同,采用复制算法,在处理器核心非常多的环境中,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。和Serial收集器一样存在Stop The World问题
4.2.4 Serial Old 收集器
Serial Old是Serial收集器的老年代版本
特点:同样是单线程收集器,采用标记-整理算法
4.3 吞吐量优先
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n
4.3.1 特点
- 多线程
- 堆内存较大,多核CPU
- 单位时间内,STW(stop the world,停掉其他所有工作线程)时间最短
- JDK1.8默认使用的垃圾回收器
4.3.2 Parallel Scavenge 收集器
与吞吐量关系密切,故也称为吞吐量优先收集器
特点:
属于新生代收集器,也是采用复制算法的收集器(用到了新生代的幸存区),又是并行的多线程收集器(与ParNew收集器类似)
该收集器的目标是达到一个可控制的吞吐量。还有一个值得关注的点是:GC自适应调节策略(与ParNew收集器最重要的一个区别)
GC自适应调节策略:
Parallel Scavenge收集器可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。
Parallel Scavenge收集器使用两个参数控制吞吐量:
- -XX:MaxGCPauseMillis :控制最大的垃圾收集停顿时间
- -XX:GCTimeRatio: 直接设置吞吐量的大小 [计算公式:1 / (1+ratio) ]
4.3.3 Parallel Old 收集器
是Parallel Scavenge收集器的老年代版本
特点:多线程,采用标记-整理算法
4.4 响应时间优先
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark
4.4.1 特点
- 多线程
- 堆内存较大,多核CPU
- 尽可能让单次STW时间变短(尽量不影响其他线程运行)
4.4.2 CMS收集器
Concurrent Mark Sweep,一种以获取最短回收停顿时间为目标的老年代收集器
特点:基于标记-清除算法实现。并发收集、低停顿,但是会产生内存碎片
CMS收集器的运行过程分为下列4步:
- 初始标记:标记GC Roots对象。速度很快但是仍存在Stop The World问题
- 并发标记:进行GC Roots Tracing 的过程,找出GC Roots对象所关联的对象且用户线程可并发执行
- 重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(可达对象变不可达)。仍然存在Stop The World问题
- 并发清除:对没有标记的对象进行清除回收
CMS收集器的内存回收过程是与用户线程一起并发执行的
4.5 Garbage First收集器
4.5.1 定义
Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
- 2004年论文发布
- 2009 JDK 6u14体验
- 2012 JDK 7u4官方支持
- 2017 JDK 9 默认
4.5.2 使用场景
- 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停时间是200ms。
- 超大堆内存,会将堆划分为多个等大的Region。
- 整体上是标记 - 整理算法,两个Region(区域)之间是复制算法。
4.5.3 相关JVM参数
-XX:+UserG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time
第一个参数:开启G1
第二个参数:设置Region大小,取值范围为1MB~32MB,且应为2的N次幂
第三个参数:设置暂停时间ms
4.5.4 G1垃圾回收阶段
第一阶段对新生代进行收集(Young Collection),第二阶段对新生代的收集同时会执行并发标记(Young Collection+ Concurrent Mark) ,第三阶段对新生代、新生代幸存区和老年区进行混合收集(Mixed Collection),以此循环。
Garbage First 将堆划分大小相等的一个个区域,每个区域都可以作为新生代、幸存区和老年代。
- E 代表伊甸园区域
- S 代表幸存区
- O 代表老年代
- Young Collection(新生代收集)
- 会STW(Stop The World),但相对于时间还是比较短的
- 新生代垃圾回收会将幸存对象以复制算法复制到幸存区
- 新生代垃圾回收会将幸存对象以复制算法复制到幸存区,幸存区存活的对象达到阈值后会以转发复制的方式进入老年代
- Young Collection+ Concurrent Mark(新生代收集+并发标记)
初始标记:找到GC Root(根对象)
并发标记:和用户线程并发执行,针对区域内所有的存活对象进行标记
- 在Young GC时会进行GC Root的初始标记
- 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由下面的JVM参数决定
-XX:InitiatingHeapOccupancyPercent=percent (默认45%)
- 当O占到45%就进行并发标记了
3.Mixed Collection(混合收集)
会对 E、S、O进行全面垃圾回收
最终标记:在并发标记的过程中,可能会漏掉一些对象,因为并发标记的同时,其他用户线程还在工作,产生一些垃圾,所以进行最终标记。清理没被标记的对象
- 最终标记(Remark)会STW
- 拷贝存活(Evacuation)会STW
-XX:MaxGCPauseMillis=ms
- 在进行混合回收时,新生代垃圾回收会将幸存对象以复制算法复制到幸存区,幸存区存活的对象达到阈值后会以转发复制的方式进入老年代,老年代中根据最大暂停时间有选择的进行回收,优先处理回收价值收益最大的那些Region,将老年代中存活下来的对象以复制算法重新赋值到一个新的老年代中
4.5.5 Full GC触发条件
1. 调用 System.gc()
只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。
2. 未指定老年代和新生代大小,堆伸缩时会产生Full GC,所以一定要配置-Xms、-Xmx
3. 老年代空间不足
老年代空间不足的常见场景比如大对象、大数组直接进入老年代、长期存活的对象进入老年代等。
为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。
除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。
还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。
在执行Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space
4. 空间分配担保失败
空间担保,下面两种情况是空间担保失败:
1、每次晋升的对象的平均大小 > 老年代剩余空间
2、Minor GC后存活的对象超过了老年代剩余空间
注意
GC日志中是否有
promotion failed
和concurrent mode failure
两种状况,当出现这两种状况的时候就有可能会触发Full GC。
promotion failed 是在进行 Minor GC时候,survivor space空间放不下只能晋升老年代,而此时老年代也空间不足时发生的。
concurrent mode failure 是在进行CMS GC过程,此时有对象要放入老年代而空间不足造成的,这种情况下会退化使用Serial Old收集器变成单线程的,此时是相当的慢的。
5. JDK 1.7 及以前的(永久代)空间满
在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。
当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。
如果经过 Full GC 仍然回收不了,那么虚拟机会抛出:java.lang.OutOfMemoryError: PermGen space
为避免以上原因引起的 Full GC,可采用的方法为增大Perm Gen或转为使用 CMS GC。
空间分配担保补充
在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次Minor GC可以确保是安全的。如果不成立,则虚拟机会先查看-XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者-XX: HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次Full GC。
4.5.6 Young Collection 跨代引用
-
卡表与Remembered Set(记忆集)
- Remembered Set存在于E中,用于保存新生代对象对应的脏卡
- 脏卡: 老年代被划分为多个区域(一个区域512K),如果该
区域引用了新生代对象,则该区域被称为脏卡
-
在引用变更时通过post-write barried(写屏障) + dirty card queue(脏卡队列)
-
concurrent refinement threads更新Remembered Set
-
新生代回收的跨代引用(老年代引用新生代)问题
在进行新生代回收时要找到GC Root根对象。有一部分GC Root对象是来自老年代,老年代存活的对象很多,如果遍历老年代找根对象效率非常低,采用卡表(Card Table)的技术,将老年代分成一个个Card,每个Card差不多512k, 老年代其中一个对象引用了新生代对象,那么就称这个Card为脏卡(dirty card)
将来进行垃圾回收时不需要找整个老年代,只需要找脏卡区就行了
4.5.7 Remark(重新标记)
pre-write barrier + satb_mark_queue
在垃圾回收时,收集器处理对象的过程中
- 黑色:已被处理,需要保留的
- 灰色:正在处理中的
- 白色:还未处理的
但是在并发标记过程中,有可能A已经被处理了还没有引用C,但该处理过程还未结束,在处理过程结束之前A引用了C,这时就会用到remark过程如下:
- 之前C未被引用,这时A引用了C,就会给C加一个写屏障,写屏障的指令会被执行,将C放入一个队列当中,并将C变为处理中状态
- 在并发标记阶段结束以后,重新标记阶段会STW,然后将放在该队列中的对象重新处理,发现有强引用引用它,就会处理它
4.5.8 JDK 8u20 字符串去重
- 优点:节省大量内存
- 缺点:略微多占用了 cpu 时间,新生代回收时间略微增加
-XX:+UseStringDeduplication #打开开关
- 将所有新分配的字符串放入一个队列
- 当新生代回收时,G1并发检查是否有字符串重复
- 如果它们值一样,让它们引用同一个 char[]
- 注意,与
String.intern()
不一样String.intern()
关注的是字符串对象- 而字符串去重关注的是 char[]
- 在 JVM 内部,使用了不同的字符串表
4.5.9 JDK 8u40 并发标记类卸载
所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸 载它所加载的所有类
-XX:+ClassUnloadingWithConcurrentMark
默认启用
4.5.10 JDK 8u60 回收巨型对象
- 一个对象大于region的一半时,就称为巨型对象
- G1不会对巨型对象进行拷贝
- 回收时被优先考虑
- G1会跟踪老年代所有incoming引用,如果老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉
4.5.11 JDK 9并发标记起始时间的调整
- 并发标记必须在堆空间占满前完成,否则退化为 FullGC
- JDK9之前需要使用
-XX:InitiatingHeapOccupancyPercent
- JDK9可以动态调整
-XX:InitiatingHeapoccupancyPercent
用来设置初始值,默认45%- 进行数据采样并动态调整
- 总会添加一个安全的空档空间
5. 垃圾回收调优
查看虚拟机运行参数
"D:\product\java\java8\bin\java" -XX:+PrintFlagsFinal -version | findstr "GC"
5.1 调优领域
- 内存
- 锁竞争
- CPU 占用
- IO
5.2 确定目标
-
【低延迟】还是【高吞吐量】,选择合适的回收器
-
CMS,G1,ZGC
-
ParallelGC
-
Zing
5.3 最快的 GC
答案是不发生 GC
- 查看 FullGC 前后的内存占用,考虑下面几个问题
- 数据是不是太多?
- resultSet = statement.executeQuery("select * from 大表 limit n")
- 数据表示是否太臃肿?
- 对象图
- 对象大小 16 Integer 24 int 4
- 是否存在内存泄漏?
- static Map map =
- 软
- 弱
- 第三方缓存实现
5.4 新生代调优
新生代的特点
- 所有new操作的内存分配非常廉价
- TLAB Thread-local Allocation Buffer
- 死亡对象的回收代价是零
- 大部分对象用过即死
- Minor GC 的时间远远低于 Full GC
调优参数:-Xmn
设置新生代的大小
设置越大越好吗?
不是,官方说明如下:
-
新生代内存太小:频繁触发
Minor GC
,Minor GC
会stop the world
暂停 ,会使得吞吐量下降 -
新生代内存太大:老年代内存占比有所降低,会更频繁地触发
Full GC
。而且触发Minor GC
时,清理新生代所花费的时间会更长
oracle建议你的新生代内存大于整个堆的25%。小于堆的50%
调优要考虑的条件:
- 新生代能容纳所有【并发量 * (请求-响应)】的数据
- 幸存区大到能保留【当前活跃对象+需要晋升对象】
- 晋升阈值配置得当,让长时间存活对象尽快晋升
- 调整最大晋升阈值:
-XX:MaxTenuringThreshold=threshold
- 垃圾回收时打印存活对象详情:
-XX:+PrintTenuringDistribution
- 调整最大晋升阈值:
5.5 老年代调优
以 CMS 为例:
- CMS 的老年代内存越大越好
- 先尝试不做调优,如果没有 Full GC 那么已经…,否则先尝试调优新生代
- 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
- 老年代空间占用达到多少比例时触发垃圾回收参数:
-XX:CMSInitiatingOccupancyFraction=percent
- 老年代空间占用达到多少比例时触发垃圾回收参数:
5.6 案例
- 案例1:Full GC 和Minor GC 频繁
说明空间紧张,如果是新生代空间紧张,当业务高峰期来了,大量对象被创建,很快新生代空间满,会经常Minor GC,而原本很多生存周期短的对象也会被晋升到老年代,老年代存了大量的生存周期短的对象,导致频繁触发Full GC,所以应该先调整新生代的内存空间大一点,让对象尽可能的在新生代
-
案例2:请求高峰期发生Full GC,单次暂停时间特别长。(CMS)
分析在哪一部分耗时较长,通过查看GC日志,比较慢的通常会是在重新标记阶段,重新标记会扫描整个堆内存,根据对象找引用,所以解决办法是在重新标记之前,先在新生代进行垃圾回收,这样就会减少重新标记的耗时时间,通过 -XX:+CMSScavengeBeforeRemark
参数在重新标记之前进行垃圾回收