垃圾回收算法
垃圾回收概述
在JVM运行期间
,会对内存中不再被使用的对象
进行分配和管理
。若不及时对内存中的垃圾进行清理,会导致被保留的空间无法被其他对象使用,从而导致内存溢出
。
内存溢出:系统无法分配给程序所
需要的指定大小内存
。内存泄漏:当
对象不再使用或无法继续使用时
,因为强引用的存在导致本该会回收的内存无法被回收
,常见于:Map用对象作为key不重写hashcode & equals & ThreadLocal内存泄漏
。
对象是否存活
JVM垃圾回收器会对对象中不再使用(死去)
的对象进行回收,那么垃圾回收器是如何进行判断的呢。
引用计数法
对于一个对象A,只要有一个对象引用了A,那么A的计数器增加1,当引用失效的时候就减1。该算法会产生对象之间循环引用
问题,会导致内存泄漏
。
可达性算法
通过一系列称为"GC Roots"
的根对象作为起点,根据引用关系向下搜索,搜索过程走过的路称为"引用链"
。如果某个对象到"GC Roots"
没有任何引用链相连,就说明该对象需要被回收。
图中绿色为
可达对象
,灰色为不可达对象
。
GC Roots
包括但不限于以下:
- 栈帧中引用的对象(局部变量、临时变量等)
- 类中的
引用型静态变量
字符串常量池中的引用
。- 被
Synchronized
锁持有的对象。
并发的可达性
我们知道大部分的收集器都是使用可达性算法
判断标记对象是否要被回收,其中又被分为中断用户线程
、与用户线程并发执行
两种。
中断用户线程
进行标记时,对象图中的对象或引用不会被修改,但堆中存储的对象越多,带来的STW
时间也会越长。因此为了减少STW
时长,标记与用户线程同时运行
能有效减少STW
时长,但会带来并发可达性问题:
被标记完毕的对象
又新引用了未被收集器访问的对象
。正在被标记的对象
直接或间接删除了未被收集器访问的对象
的引用。
基于上述两个问题产生了两种解决方案:
- 增量更新
基于问题1,当被标记完毕对象
又引用了未被收集器访问的对象
时,将这些被标记完毕对象
记录下来,等并发标记阶段结束后,以这些被标记完毕对象为根
再次进行扫描。CMS收集器
采用此策略实现并发标记。
- 原始快照
基于问题2,当正在被标记的对象
直接或间接删除了未被收集器访问的对象
的引用时,将这些正在被标记的对象
记录下来,等并发标记结束后,以这些正在被标记的对象
为根重新扫描。G1收集器
采用此策略实现并发标记。
Java中的引用
传统的引用概念:若reference
类型的数据中存储的数值是另一块内存的起始地址,就说明该reference
数据是某个内存、某个对象的引用。
从JDK1.2
开始,Java对引用的概念进行补充,将引用分为了:强引用、软引用、弱引用和虚引用
四种。
public abstract class Reference<T> {
// 引用本身
private T referent;
// 存储reference本身的链表队列
volatile ReferenceQueue<? super T> queue;
}
当垃圾回收器准备回收一个对象时,发现它还有
软、弱、虚引用
,就会在回收对象之前,将该引用加入到与之关联的引用队列ReferenceQueue
中去,这样就可以实现在引用对象回收前的相关操作。
- 强引用
即最传统引用的体现,比如Object obj = new Object()
,只要强引用关系存在,那么垃圾回收器永远不会回收掉被引用的对象。
- 软引用
用于描述还有用、但非必须的对象
,只被软引用关联(指向)
的对象,在OOM之前,会将这些对象进行二次回收,如果回收后仍没有足够内存,才会抛出OOM。Java中用SoftReference
实现。
- 弱引用
相比软引用
,只被弱引用关联(指向)
的对象只能生存到下一次垃圾收集,只要垃圾回收器工作,弱引用
就会被回收。Java中用WeakReference
实现。ThreadLocal.ThreadLocalMap<k,v>中key就继承了弱引用
。
public class Weak {
private static WeakReference<String> weakReference;
public static void main(String[] args) {
test();
System.out.println(weakReference.get());// hello
System.gc(); // test作用域结束,gc会清理weakReference
System.out.println(weakReference.get());// null
}
static void test() {
// str 作为test方法的本地变量
String str = new String("hello");
weakReference = new WeakReference<>(str);
System.gc();// 不会被清理
System.out.println(weakReference.get());// hello
}
}
和软引用一样,弱引用也适合保存
可有可无的数据
,当系统内存不足的时候会被回收,内存充足的时候,缓存数据存在相当长的时间,达到让系统加速的作用。
- 虚引用
引用中最弱的一种,一个对象是否有虚引用
的存在,对其生存时间不会产生影响,并且无法通过虚引用获取对象实例
。唯一的作用就是为了在该对象被回收时收到通知
。
public class Phantom {
// 实现 包含虚引用的对象在回收时接受通知
public static void main(String[] args) {
String hello = new String("hello");
// 创建引用Queue
ReferenceQueue<String> queue = new ReferenceQueue<>();
PhantomReference<String> reference = new PhantomReference<>(hello, queue);
new Thread(() -> {
while (true) {
Reference<? extends String> poll = queue.poll();
if (poll != null) {
try {
// 此时说明hello对象被回收了
Field referent = Reference.class
.getDeclaredField("referent");
referent.setAccessible(true);
String str = (String) referent.get(poll);
System.out.println("GC Will Collect " + str);
break;
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}
}).start();
// 去除hello的引用
hello = null;
// 调用垃圾回收对象
System.gc();
}
}
对于虚引用,它的get()方法只会返回null,因为
虚引用是不可达的
。javapublic class PhantomReference<T> extends Reference<T> { public T get() { return null; } }
对象的回收
我们知道HotSpot
采用的是可达性算法
判断对象是否存活,那么不再存活的对象垃圾回收器是如何回收的呢?
- 垃圾回收器对对象进行回收时,先通过
可达性算法
判断该对象是否可达。 - 如果
对象不可达
并且复写了finalize方法且该对象的finalize方法之前没被调用过
。 - 垃圾回收器会将对象放置到
ReferenceQueue<Finalizer>
队列中,稍后由JVM启动一个低优先级的Finalizer线程
去执行Queue中对象的finalize方法
。 - 但JVM不一定会等待
finalize
执行结束,因为如果finalize
方法卡顿,会导致队列中后续的对象处于等待,甚至导致整个内存回收系统的崩溃
。 - 若该对象的finalize方法不能
将对象与引用链建立连接
,该对象会被垃圾回收器清理。
public class ReachabilityAnalysis {
// 创建GC Roots
private static ReachabilityAnalysis REACHABILITY_ANALYSIS = null;
private void isAlive() {
System.out.println("i'm still alive ...");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("execute finalize method ...");
// 试图和引用链建立联系
REACHABILITY_ANALYSIS = this;
}
public static void main(String[] args) throws InterruptedException {
// 创建对象
REACHABILITY_ANALYSIS = new ReachabilityAnalysis();
// 去除引用链的联系, 便于测试
REACHABILITY_ANALYSIS = null;
// 调用gc时 对象第一次尝试自救
System.gc();
// 因为finalizer线程的低优先级, 需要休眠一会。
// JVM会先判断是否有必要执行finalizer方法, 并执行相应的finalize()方法
Thread.sleep(1_000);
if (null != REACHABILITY_ANALYSIS) {
REACHABILITY_ANALYSIS.isAlive();
} else {
System.out.println("i'm dead ...");
}
// 第二次自救 用于判断是否会执行finalize方法两次
REACHABILITY_ANALYSIS = null;
System.gc();
Thread.sleep(1_000);
if (null != REACHABILITY_ANALYSIS) {
REACHABILITY_ANALYSIS.isAlive();
} else {
System.out.println("i'm dead ...");
}
// 结论: 任何对象的finalize()方法只会被系统调用一次
}
}
对象
finalize
方法只会被JVM调用一次,只要执行finalize
方法重新与引用链
建立联系,就不会被清理。不建议使用finalize进行释放资源
,因为可能发生引用外泄,无意中复活对象。并且finalize调用时间不确定,相比之下更推荐finally释放资源
。
垃圾回收算法
标记清除法(Mark-Sweep)
包含标记阶段
和清除阶段
。标记阶段通过可达性算法
标记分析可达对象,清除阶段会清除所有未被标记的对象
。 此方法会产生空间碎片
。并且回收后的空间是不连续
的,会导致工作效率低于连续空间。该算法更关注垃圾回收器的耗时
操作。
复制算法(Copying)
将内存空间分为两份
,在进行垃圾回收时,将正在使用的那一份内存中的活对象
复制到另一份内存中,之后清除正在使用的内存块中的所有对象
,交换两个内存的角色,完成垃圾回收。 相对于标记清除算法
,复制算法
不会产生空间碎片,但会导致使用的内存只有一半
。
新生代串行垃圾回收器
使用了该算法,它将新生代分为eden、from(s0)、to(s1)
三个区域,GC在回收对象时,会先将eden & s0
区域的对象复制到s1(大对象、老年对象、s1区域满时对象会直接进入老年代)
,然后清空eden & s0
区域,再将s0 & s1互换
,保证s1永远为空
。
标记压缩算法(Mark-Compact)
在标记阶段,使用可达性算法
对所有可达对象进行标记。在清除阶段,将所有的存活对象压缩到内存的一端,然后清除边界外的所有空间。相比之前的算法,避免了内存碎片
的产生且不需要内存折半
,但是移动大对象会给系统带来较长时间的STW
。该算法更关注垃圾回收器的吞吐量
操作。
STW:垃圾回收器工作时,Java程序需要暂停工作,等待垃圾回收完成,这种现象叫做
Stop The World
。
分代算法(Generational Collecting)
基于两个分代假说之上:
弱分代假说:绝大多数的对象都是朝生夕灭的。
强分代假说:熬过多次垃圾回收的对象就越难以消亡。
奠定了垃圾收集器的一致设计原则:收集器应将Java堆分成不同的区域,然后将回收对象依据其年龄(对象熬过垃圾回收的次数)分配到不同的区域中存储
。
但因为对象会存在跨代引用
,即新生代对象完全可能被老年代对象引用
,因此除了必要的可达性分析外,还需要遍历老年代对象
来保证所有对象可达性分析结果的准确性。基于此理论提出了跨代引用假说:
跨代引用相对于同代引用来说仅占极少部分。
并基于此假说采用了如下设计:在新生代上建立一个全局数据结构记忆集(Remembered Set)
,该结构将老年代划成若干小块,标识出老年代哪一块内存会存在跨代引用,当发生新生代垃圾回收时,会对记忆集中的记录加入GC Roots
进行扫描,相比扫描整个老年代来说大大的减少了运行时开销。
分区算法(Region)
分区算法将整个堆空间分成连续的不同小区间,每个小区间都独立使用,独立回收
。控制一次回收小区间的数量,能够有效减少GC产生的停顿。
分区回收算法带来的跨代引用问题:
因为对象会存在
新生代、老年代间的跨代引用问题
,垃圾收集器建立了名为记忆集(Remember Set)
的数据结构,用于记录非收集区域指向收集区域的指针集合
的抽象数据结构。继而避免对整个老年代进行扫描
。
卡表(Card Table)
是最常见的实现记忆集
结构的方式。可以是字节数组
或哈希表
,存储的是跨代引用的对象的内存地址
,这样只需要筛选出跨代引用的对象,将其加入GC Roots中一起扫描即可。
垃圾回收期器
JVM中经典的垃圾回收器
下图是来自oracle官方博客中介绍垃圾回收器之间的关系图。
黄色代表
新生代
,灰色代表老年代
,两个垃圾回收器之间相连表示这两个垃圾回收器组合使用
。
Serial & CMS
与ParNew & Serial Old
两组 在JDK8
中已过期,JDK9
中已移除。我们用
并行
、并发
来形容不同的收集器:并行:描述的是多条垃圾回收器线程之间的关系,默认此时的
用户线程处于等待
状态。并发:描述垃圾回收器线程与用户线程间的关系,说明同一时间
垃圾回收器线程与用户线程都在运行
。
新生代收集器
以下三种收集器都采用的是标记-复制算法
来实现收集器的回收逻辑。
Serial收集器
使用单线程
工作的收集器,除了只会用一个处理器或一个收集线程
去完成垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停其他所有的工作线程(STW)
,直到收集结束。是客户端模式
下默认新生代收集器。
客户端/服务端区别:client比server模式
启动速度更快
,当server比client模式运行速度更快
。
相比其他垃圾收集器,Serial收集器
时所有垃圾回收器里面额外内存消耗最小的
,但STW耗时是最长的
;对于单核处理器或处理器核心较少
环境来说,由于没有线程交互的开销,Serial收集器
可以获得最高的单线程收集效率
。
-XX:+UseSerialGC 新生代 & 老年代都使用串行收集器
ParNew收集器
ParNew收集器
本质上是Serial收集器
的多线程并行
版本。除了同时使用多条线程
进行垃圾收集外,其余的和Serial收集器
一致。
这里的并行指的是:
同一个时间有多个
这样的收集线程在协调工作,用户线程此时处于等待状态。
除了Serial收集器
外,只有ParNew收集器
能与CMS收集器
配合工作。
# 新生代ParNew & 老年代CMS 是开启CMS下新生代默认收集器
-XX:+UseConcMarkSweepGC
# 新生代ParNew & 老年代SerialOld(JDK8后已过期)
-XX:+UseParNewGC
因为线程交互的开销,在单核处理器
下性能低于Serial
,但是多核心
下ParNew
收集器还是很高效的。
# 垃圾收集的线程数为8
-XX:ParallelGCThreads=8
不设置此参数时,当
Cpu Cores < 8
时,Threads=Cpu Cores
,否则Threads=3+(5*cores)/8)
。
Parallel Scavenge收集器
相比ParNew收集器
目标是减少用户线程的停顿时间
,Paraller收集器
关注则是可控制的吞吐量
。
假设JVM执行完成某个请求共需要100分钟,其中垃圾收集花费1分钟,那么吞吐量就是
99%
。
低停顿时间
适合用户交互或保证服务响应
的程序。高吞吐量
适合最高效率
利用处理器资源,尽快
完成程序的运算任务
。
停顿时间
缩短是以牺牲吞吐量和新生代空间
为代价的。如果我们将新生代设置的较小,虽然会减少每次回收的时间,但是会导致垃圾回收更加频繁,虽然停顿时间在减少,但是吞吐量在下降。
# 允许设置一个大于0的毫秒数,收集器尽量保证内存回收时间不超过该值
-XX:MaxGCPauseMillis
# 允许设置一个大于0小于100的整数n
# 系统将花费不超过 1/(1+n)的时间进行回收 假设n=99,那么不超过1%时间进行回收。
-XX:GCTimeRatio
# 自适应GC策略,自动调整新生代大小,老年代晋年龄等 区别于ParNew是Paraller独有
-XX:+UseAdaptiveSizePolicy
垃圾回收器
老年代收集器
Serial Old收集器
是Serial
收集器的老年代版本,基于标记-整理
的单线程
收集器,用于客户端
模式下的HotSpot虚拟机使用。
在服务端
模式下,有两个用途:JDK5及之前版本中与Parallel Scavenge
配合使用;作为CMS
收集器发生失败时的备用收集器。
Parallel Old收集器
JDK6推出,是Parallel Scavenge
收集器的老年代版本,支持多线程并发
收集,基于标记-整理
算法实现。
Parallel Old
配合Parallel Scavenge
的组合,用于注重吞吐量和处理器资源较为稀缺的
情况。
# Parallel Scavenge + Parallel Old,JDK8默认组合
-XX:+UseParallelGC
CMS收集器
CMS(Concurrent Mark Sweep
)收集器是一种以获取最短回收停顿时间
为目标的收集器。基于标记-清除
算法。
收集流程
- 初始标记
此阶段仅标记GC Roots能直接关联到的对象
。需要停顿用户线程(STW)。
- 并发标记(并发)
基于初始标记
阶段标记的从GC Roots可直接关联的对象开始
遍历整个对象图的过程。不需要停顿用户线程,与垃圾回收线程一起工作。
- 重新标记(并行)
该阶段是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那部分对象的标记记录。CMS是使用增量更新
来解决并发标记产生的问题。
- 并发清除(并发)
清理删除掉标记阶段的已死亡对象,此阶段不需要移动存活对象。
CMS收集器缺点
- 对处理器资源敏感
因为CMS的并发阶段会占用一部分线程
会导致应用程序变慢,降低吞吐量。默认回收线程数是(Cpu Cores + 3 ) / 4
,若Cpu Cores越小,那么对程序运行的影响较大。
- 无法处理浮动垃圾
在CMS并发标记和并发清理
阶段,用户线程还在继续执行,就会有新的垃圾对象
不断产生,但这些对象出现在初始标记阶段后,只能在下次垃圾回收中再处理这部分垃圾。
因为CMS收集器
并发标记和并发清理的特性,必须预留一些空间
提供给用户线程使用,不能等老年代满再工作。
# 当老年代使用了68%后CMS开始工作
# JDK5默认68%,JDK6默认92%
-XX:CMSInitiatingOccupancyFraction=68
如果预留的内存不够用户线程分配新对象,会启用
Serial Old
进行Major GC
。会带来较长的停顿时间。
- 产生大量空间碎片
因为CMS基于比较-清除
算法,易产生大量的空间碎片,在无法给大对象分配内存时导致一次Full Gc
。
# 默认开启,当CMS进行Full GC时开启内存碎片合并整理的过程 JDK9废弃
-XX:+UseCMSCompactAtFullCollection
# CMS在执行若干次不整理空间的Full GC后,下一次进行碎片整理 JDK9废弃
-XX:CMSFullGCBeforeCompaction
Full GC:对整个Java堆进行回收,包含新生代和老年代
Minor GC:对新生代进行回收。
Major GC:对老年代进行回收。
G1收集器
概念
G1(Garbage First)收集器
开创了面向局部收集的设计思路
和基于Region的内存布局形式
。目的是为了实现支持停顿时间模型
的收集器。基于标记-整理
算法。
停顿时间模型:支持指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒。
相比于其他收集器要么面向新生代,要么面向老年代,而G1面向堆内存任何部分来组成回收集(Collection Set)
进行回收。衡量标准由属于哪个分代变为哪块内存垃圾最多,回收收益最大。
相比于之前的固定大小和数量的区域划分的收集器,G1将堆内存分为多个大小相等的独立区域(Region,默认分成2048份)
,每个Region
可以根据需要扮演Eden、Survivor或老年代空间
。
Region
中还存在一类特殊的Humongous
区域,用于存储大对象,G1认为只要大小超过一个Region
容量一半的对象即为大对象,对于超过Region
大小的对象,将会被存放在N个连续的Humongous
区域中。
# 设置Region的大小(单位: B),在[1MB,32MB]必须为2的幂次方
# 设置Region的大小=2097152B=2MB,不设置默认是1MB
-XX:G1HeapRegionSize=2097152
G1仍然保留新生代、老年代的概念,当它们不再是固定的区域了,改为一系列区域(不需要连续)的动态集合
。G1在后台维护一个优先级列表
,每次根据用户通过-XX:MaxGCPauseMillis
指定的停顿时长(默认200ms)
,优先回收价值收益最大的Region
,以达到最大的收集效率。
收集流程
- 初始标记
和CMS类似,标记GC Roots
直接关联的对象,此阶段会产生STW
。
- 并发标记(并发)
基于初始标记
,从GC Roots
开始对堆中对象进行可达性分析,并递归扫描整个对象图
,找出可回收对象,此过程可与用户程序并发执行。
- 最终标记(并行)
对用户线程做另一个短暂的暂停,通过原始快照SATB
处理并发标记导致的并发可达性问题(上章分析过)。
- 筛选回收(并行)
负责更新Region
的统计数据,并按照回收价值和成本进行排序,根据用户期望的停顿时间
来指定回收计划。可见多个Region
合并成回收集(Collection Set)
,将回收的Region
中的对象移到空的Region
,再清理旧的Region
,设计到对象的移动(体现标记-整理
算法),此阶段用户线程暂停
,多条收集器线程并行
完成。
G1vsCMS
- 优势
- 指定最大停顿时间
- 不会产生内存空间碎片
- 劣势
- 并发执行带来的较高的内存占用和负载
- 每个
Region
都持有一份卡表
导致堆内存的消耗。
对象在堆中的分配
前面我们了解到,大部分
的对象都是在堆中
进行内存分配,但堆中又存在多个逻辑区域(新生代、老年代),所以这章我们就要讨论下,对象在堆中的进行内存分配的基本原则。
TLAB
在讨论对象分配前,我们需要对之前引入的TLAB
的概念进一步解析。TLAB(本地线程缓冲)
,其存在的目的是为了加速对象的分配,即每个线程都拥有自己的专属区域进行对象分配,来避免多线程冲突,默认是启动的。
# 开/关TLAB
-XX:+/-UseTLAB
# 设置TLAB大小
-XX:TLABSize
# 查看TLAB信息
-XX:+PrintTLAB
# 对象占TLAB空间的比例,大于此比例堆中分配,小于就废弃当前
## TLAB区域,并新建一个TLAB存放,默认64
-XX:TLABRefillWasteFraction=64
# 默认情况TLAB和refill_waste是动态的,关闭TLAB动态调整
-XX:-ResizeTLAB
我们假设TLAB大小为100KB,第一次分配给对象80KB,此时还剩20KB,如果第二次有30KB大小的对象需要分配,此时有两种选择:
- 废弃所剩的20KB区域,新建一个TLAB存放30KB的对象。
- 将30KB对象分配在堆上,保留所剩的20KB区域,等到下次有小于20KB对象分配时再使用该区域。
-XX:TLABRefillWasteFraction=64
,即允许TLAB空间浪费的比例,当对象/TLAB的比例
大于64,对象在堆中分配,小于64则会开辟新TLAB存放。
一般在eden中分配
大部分情况下,对象在Eden区中进行分配
,如果Eden区
空间不够,JVM会发起一次Minor GC
。
大对象进入老年代
大对象:需要大量连续内存空间的Java对象
或新生代已无足够空间分配的对象直接进入老年代。
# 将大于此大小的对象直接分配到老年代
-XX:PretenureSizeThreshold=5242880(5mb)
只适用于
Serial、Serial Old、ParNew
三种收集器。
长期存活对象进入老年代
长期存活的对象将进入老年代。对象通常在eden区诞生,如果经历了一次Minor Gc
后仍然存活,并能够被s0
容纳,该对象会被移动到s0
区并将其对象头中的对象年龄 + 1
。当年龄达到阈值,就会进入老年代。
# 对象晋升到老年代的年龄阈值
-XX:MaxTenuringThreshold=15
动态对象年龄判断
:JVM不是永远要求对象年龄达到
-XX:MaxTenuringThreshold
指定的值才能晋升老年代:如果
s0中相同年龄的对象大小总
和大于s0区域的一半(-XX:TargetSurvivorRatio决定,默认50)
,那么大于等于该年龄的对象
就会进入老年代。
总结
对象的内存分配流程需要经历栈上分配 -> TLAB分配 -> 是否进入老年代 -> 最终eden分配
。
5. 虚拟机参数概述
基本参数
参数 | 作用 |
---|---|
-XX:+PrintGCDetails | 打印详细的GC日志 |
-XX:+PrintGCTimeStamps | GC开头的时间为虚拟机启动时间的偏移量 |
-XX:+PrintGCApplicationStoppedTime | 打印引用程序由于GC而产生停顿的时间 |
-Xloggc:D://log.txt | 输出GC日志到D盘下log.txt文件中 |
-XX:+PrintVMOptions | 打印显示传递的参数 |
-XX:+PrintCommandLineFlags | 打印传递给虚拟机的显式和隐式 参数 |
-XX:+PrintFlagsFinal | 打印全部参数(包括虚拟机自身的参数) |
-Xss1m | 指定栈大小为1m |
-Xms10m | 初始堆空间大小 |
-Xmx20m | 最大堆空间大小 |
-Xmn2m | 新生代大小 |
-XX:SurvivorRatio | 新生代中eden/s0/s1比例,默认8:1:1 |
-XX:NewRatio | 老年代/新生代的比例,默认2:1 |
-XX:NewSize | 新生代初始大小 |
-XX:MaxNewSize | 新生代大小最大值 |
-XX:+HeapDumpOnOutOfMemoryError | 堆OOM时导出堆的信息 |
-XX:HeapDumpPath=D://log.dump | 将OOM信息导入到D盘下log.dump文件中 |
-XX:MetaspaceSize=1m | 设置元数据区初始大小为1m |
-XX:MaxMetaspaceSize=2m | 设置元数据区大小最大为2m |
-XX:MaxDirectMemorySize=2m | 本机直接内存(堆外内存)最大2m,默认等于-Xmx |
-XX:+UseTLAB | 开启TLAB,默认开启 |
-XX:+PrintTLAB | 打印TLAB信息 |
-XX:TLABSize=1024 | 设置TLAB大小为1kb |
-XX:TLABRefillWasteFraction=64 | 允许TLAB空间浪费的比例 |
-XX:-ResizeTLAB | 禁止TLAB自动调整大小和浪费比例 |
-XX:PretenureSizeThreshold=5242880 | 大于5m对象直接进入老年代,只对Serial、ParNew有用 |
-XX:MaxTenuringThreshold=15 | 晋升到老年代的年龄大小 |
-XX:TargetSurvivorRatio=50 | 用于动态对象年龄 判断的s0的使用率参数,默认50 |
收集器选择参数
参数 | 新生代 | 老年代 |
---|---|---|
-XX:+UseSerialGC | Serial | Serial Old |
-XX:+UseParallelGC | Parallel Scavenge | Parallel Old |
-XX:+UseParNewGC | ParNew | Serial Old |
-XX:+UseConcMarkSweepGC | ParNew | CMS |
-XX:UseG1GC | G1 | G1 |
收集相关参数
收集器 | 相关参数 | 注释 |
---|---|---|
ParNew Parallel CMS G1 Parallel Old | -XX:ParallelGCThreads=n | 指定并行回收线程数n=cores<8?cores:3+((5*cores))/8) |
Parallel | -XX:MaxGCPauseMillis=n -XX:GCTimeRatio=n -XX:+UseAdaptiveSizePolicy | 最大回收停顿时长 不超过 1/1+n 时间进行回收自适应GC策略 |
CMS | -XX:CMSInitiatingOccupancyFraction=n -XX:CMSFullGCBeforeCompaction=n -XX:+UseCMSCompactAtFullCollection | 老年代容量到n时CMS开始工作,默认92 CMSn次FullGC后开启碎片整理,默认0 CMS进行FullGC时开启碎片整理 |
G1 | -XX:G1HeapRegionSize -XX:MaxGCPauseMillis -XX:InitiatingHeapOccupancyPercent | 指定Region大小,默认1MB,最大32MB 垃圾收集时停顿时长,默认200ms 堆使用率达到n后开启并发标记,默认45 |