目录
一、GC tuning
1、什么是垃圾
2、如何定位垃圾
3、常见的垃圾回收算法
4、JVM内存分代模型(用于分代垃圾回收算法)
5、常见的垃圾回收器
常见垃圾回收器组合参数设定(1.8)
6、JVM常用命令行参数
没有任何引用指向的一个对象或者多个对象(循环引用)
1. 引用计数(ReferenceCount)
2. 根可达算法(RootSearching)
which instances are roots?
1.JVM stack,
2.native method stack,
3.runtime constant pool,
4.static references in method area,
5.Clazz
6.JNI指针
1. 标记清除(mark sweep) - 位置不连续产生碎片 效率偏低(两遍扫描)
2. 拷贝算法 (copying) - 没有碎片,浪费空间
3. 标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)
1. 部分垃圾回收器使用的模型
除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外不仅逻辑分代,而且物理分代
2. 新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace
1. 永久代 元数据 - Class
2. 永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)
3. 字符串常量 1.7 - 永久代,1.8 - 堆
4. MethodArea逻辑概念 - 永久代、元数据
3. 新生代 = Eden + 2个suvivor区
1. YGC回收之后,大多数的对象会被回收,活着的进入s0
2. 再次YGC,活着的对象eden + s0 -> s1
3. 再次YGC,eden + s1 -> s0
4. 年龄足够 -> 老年代 (15 CMS 6)
5. s区装不下 -> 老年代
4. 老年代
1. 顽固分子
2. 老年代满了FGC Full GC
5. GC Tuning (Generation)
1. 尽量减少FGC
2. MinorGC = YGC
3. MajorGC = FGC栈上分配
– 线程私有小对象
– 无逃逸
– 支持标量替换
– 无需调整
线程本地分配TLAB (Thread Local Allocation Buffer) – 占用eden,默认1%
– 多线程的时候不用竞争eden就可以申请空间,提高效率
– 小对象
– 无需调整public class TestTLAB { class User { int id; String name; public User(int id, String name) { this.id = id; this.name = name; } } void alloc(int i) { new User(i, "name " + i); } public static void main(String[] args) { TestTLAB t = new TestTLAB(); long start = System.currentTimeMillis(); for (int i = 0; i < 1000_0000; i++) t.alloc(i); long end = System.currentTimeMillis(); System.out.println(end - start); } }
直接运行结果:400左右
去掉栈上分配,运行时在VM arguments:配置参数:-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB
运行结果在750左右
动态年龄
s1 + Eden- > s2超过50%把年龄最大的放入OLD原文链接:jvm误区--动态对象年龄判定 - 简书
虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
学习jvm的人,基本都阅读过上面这段话,这里讲的是动态年龄的判定。对于动态的判定的条件就是相同年龄所有对象大小的总和大于Survivor空间的一半,然后算出的年龄要和MaxTenuringThreshold的值进行比较,以此保证MaxTenuringThreshold设置太大(默认15),导致对象无法晋升。
问题的提出
场景假设
如果说非得相同年龄所有对象大小总和大于Survivor空间的一半才能晋升。我们看下面的场景
MaxTenuringThreshold为15
年龄1的对象占用了33%
年龄2的对象占用33%
年龄3的对象占用34%。
开始推论
按照晋升的标准。首先年龄不满足MaxTenuringThreshold,不会晋升。
每个年龄的对象都不满足50%。,不会晋升。
得到假设结论
Survivor都占用了100%了,但是对象就不晋升。导致老年代明明有空间,但是对象就停留在年轻代。但这个结论似乎与jvm的表现不符合,只要老年代有空间,最后还会晋升的。
问题的解答uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) { //survivor_capacity是survivor空间的大小 size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100); size_t total = 0; uint age = 1; while (age < table_size) { total += sizes[age];//sizes数组是每个年龄段对象大小 if (total > desired_survivor_size) break; age++; } uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; ... }
我把晋升年龄计算的代码摘出。我们来看看动态年龄的计算。代码中有一个TargetSurvivorRatio的值。
-XX:TargetSurvivorRatio 目标存活率,默认为50%
通过这个比率来计算一个期望值,desired_survivor_size 。
然后用一个total计数器,累加每个年龄段对象大小的总和。
当total大于desired_survivor_size 停止。
然后用当前age和MaxTenuringThreshold 对比找出最小值作为结果
总体表征就是,年龄从小到大进行累加,当加入某个年龄段后,累加和超过survivor区域*TargetSurvivorRatio的时候,就从这个年龄段网上的年龄的对象进行晋升。
再次推演
还是上面的场景。
年龄1的占用了33%,年龄2的占用了33%,累加和超过默认的TargetSurvivorRatio(50%),年龄2和年龄3的对象都要晋升。
小结
动态对象年龄判断,主要是被TargetSurvivorRatio这个参数来控制。而且算的是年龄从小到大的累加和,而不是某个年龄段对象的大小。看完后先记住这个参数吧TargetSurvivorRatio,虽然你以后基本不会调整他。
分配担保:YGC期间 survivor区空间不够了 空间担保直接进入老年代
原文链接:JVM内存分配担保机制-腾讯云开发者社区-腾讯云
1. JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS
并发垃圾回收是因为无法忍受STW
2. Serial 年轻代 串行回收
3. PS 年轻代 并行回收
4. ParNew 年轻代 配合CMS的并行回收
5. SerialOld
6. ParallelOld
7. ConcurrentMarkSweep 老年代并发的,垃圾回收和应用程序同时运行,降低STW的时间(200ms)
CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定
CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收
想象一下:
PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW)
几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC
算法:三色标记 + Incremental Update
8. G1(10ms)
算法:三色标记 + SATB
9. ZGC (1ms) PK C++
算法:ColoredPointers + LoadBarrier
10. Shenandoah
算法:ColoredPointers + WriteBarrier
11. Eplison
12. PS 和 PN区别的延伸阅读:
https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html
13. 垃圾收集器跟内存大小的关系
1. Serial 几十兆
2. PS 上百兆 - 几个G
3. CMS - 20G
4. G1 - 上百G
5. ZGC - 4T - 16T(JDK13)1.8默认的垃圾回收:PS + ParallelOld
1、-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
2、-XX:+UseParNewGC = ParNew + SerialOld
3、-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
4、-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
5、-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
6、-XX:+UseG1GC = G1
7、windows中默认GC的查看方法
java -XX:-PrintCommandLineFlags -version
C:\Users\Administrator>java -XX:+PrintCommandLineFlags -version -XX:InitialHeapSize=132790272 -XX:MaxHeapSize=2124644352 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC java version "1.8.0_202" Java(TM) SE Runtime Environment (build 1.8.0_202-b08) Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
Serial
a stop-the-world, copying collector which uses a single GC threadParallel Scavenge
a stop-the-world, copying collector which uses multiple GC threadsParNew
a stop-the-world, copying collector which uses multiple GC threads
It differs from "Parallel Scavenge" in that it has enhancements that make it usable with CMS
For example, "ParNew" does the synchronization needed so that it can run during the concurrent phases of CMS.Serial Old
a stop-the-world,mark-sweep-compact collector that uses a single GC thread.parallel old
a compacting collector that uses multiple GC threads.CMS
concurrent mark sweep
a mostly concurrent, low-pause collector.
4 phases
1. initial mark
2. concurrent mark
3. remark
4. concurrent sweep
JVM的命令行参数参考
HotSpot参数分类
标准: - 开头,所有的HotSpot都支持
非标准:-X 开头,特定版本HotSpot支持特定命令
不稳定:-XX 开头,下个版本可能取消
java -version
java -X
java -XX:+PrintFlagsFinal
C:\Users\Administrator>java -X
-Xmixed 混合模式执行 (默认)
-Xint 仅解释模式执行
-Xbootclasspath:<用 ; 分隔的目录和 zip/jar 文件>
设置搜索路径以引导类和资源
-Xbootclasspath/a:<用 ; 分隔的目录和 zip/jar 文件>
附加在引导类路径末尾
-Xbootclasspath/p:<用 ; 分隔的目录和 zip/jar 文件>
置于引导类路径之前
-Xdiag 显示附加诊断消息
-Xnoclassgc 禁用类垃圾收集
-Xincgc 启用增量垃圾收集
-Xloggc: 将 GC 状态记录在文件中 (带时间戳)
-Xbatch 禁用后台编译
-Xms 设置初始 Java 堆大小
-Xmx 设置最大 Java 堆大小
-Xss 设置 Java 线程堆栈大小
-Xprof 输出 cpu 配置文件数据
-Xfuture 启用最严格的检查, 预期将来的默认值
-Xrs 减少 Java/VM 对操作系统信号的使用 (请参阅文档)
-Xcheck:jni 对 JNI 函数执行其他检查
-Xshare:off 不尝试使用共享类数据
-Xshare:auto 在可能的情况下使用共享类数据 (默认)
-Xshare:on 要求使用共享类数据, 否则将失败。
-XshowSettings 显示所有设置并继续
-XshowSettings:all
显示所有设置并继续
-XshowSettings:vm 显示所有与 vm 相关的设置并继续
-XshowSettings:properties
显示所有属性设置并继续
-XshowSettings:locale
显示所有与区域设置相关的设置并继续
-X 选项是非标准选项, 如有更改, 恕不另行通知。
C:\Users\Administrator>java -XX:+PrintFlagsFinal
[Global flags]
intx ActiveProcessorCount = -1 {product}
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
.....
{product}
intx hashCode = 5 {product}
用法: java [-options] class [args...]
(执行类)
或 java [-options] -jar jarfile [args...]
(执行 jar 文件)
其中选项包括:
-d32 使用 32 位数据模型 (如果可用)
-d64 使用 64 位数据模型 (如果可用)
-server 选择 "server" VM
默认 VM 是 server.
-cp <目录和 zip/jar 文件的类搜索路径>
-classpath <目录和 zip/jar 文件的类搜索路径>
用 ; 分隔的目录, JAR 档案
和 ZIP 档案列表, 用于搜索类文件。
-D<名称>=<值>
设置系统属性
-verbose:[class|gc|jni]
启用详细输出
-version 输出产品版本并退出
-version:<值>
警告: 此功能已过时, 将在
未来发行版中删除。
需要指定的版本才能运行
-showversion 输出产品版本并继续
-jre-restrict-search | -no-jre-restrict-search
警告: 此功能已过时, 将在
未来发行版中删除。
在版本搜索中包括/排除用户专用 JRE
-? -help 输出此帮助消息
-X 输出非标准选项的帮助
-ea[:...|:]
-enableassertions[:...|:]
按指定的粒度启用断言
-da[:...|:]
-disableassertions[:...|:]
禁用具有指定粒度的断言
-esa | -enablesystemassertions
启用系统断言
-dsa | -disablesystemassertions
禁用系统断言
-agentlib:[=<选项>]
加载本机代理库 , 例如 -agentlib:hprof
另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:[=<选项>]
按完整路径名加载本机代理库
-javaagent:[=<选项>]
加载 Java 编程语言代理, 请参阅 java.lang.instrument
-splash:
使用指定的图像显示启动屏幕
有关详细信息, 请参阅 http://www.oracle.com/technetwork/java/javase/documentation/index.html。
测试程序:
import java.util.LinkedList;
import java.util.List;
public class HelloGC {
public static void main(String[] args) {
System.out.println("HelloGC!");
List list = new LinkedList<>();
for (;;) {
byte[] b = new byte[1024 * 1024];
list.add(b);
}
}
}
设置VM参数:-Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC
执行输出日志:
-XX:InitialHeapSize=41943040 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
HelloGC!
[GC (Allocation Failure) 8152K->7888K(39936K), 0.0042793 secs]
[GC (Allocation Failure) 15216K->15024K(39936K), 0.0023240 secs]
[GC (Allocation Failure) 22345K->22128K(39936K), 0.0020152 secs]
[GC (Allocation Failure) 29452K->29344K(39936K), 0.0017425 secs]
[Full GC (Ergonomics) 29344K->29202K(55808K), 0.0100634 secs]
[GC (Allocation Failure) 36527K->36594K(55808K), 0.0016723 secs]
[GC (Allocation Failure) 43913K->43762K(55808K), 0.0015158 secs]
[Full GC (Ergonomics) 43762K->43539K(60416K), 0.0021737 secs]
[GC (Allocation Failure) 50861K->50931K(60416K), 0.0017837 secs]
[Full GC (Ergonomics) 50931K->50707K(60416K), 0.0020457 secs]
[Full GC (Ergonomics) 58023K->57875K(60416K), 0.0023809 secs]
[Full GC (Allocation Failure) 57875K->57863K(60416K), 0.0081151 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.lwz.xxx.xxx.HelloGC.main(HelloGC.java:12)
linux上操作
1. 区分概念:内存泄漏memory leak,内存溢出out of memory
2. java -XX:+PrintCommandLineFlags HelloGC
3. java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
PrintGCDetails PrintGCTimeStamps PrintGCCauses
4. java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
5. java -XX:+PrintFlagsInitial 默认参数值
6. java -XX:+PrintFlagsFinal 最终参数值
7. java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
8. java -XX:+PrintFlagsFinal -version |grep GC
PS GC日志详解
每种垃圾回收器的日志格式是不同的!
heap dump部分:
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址
JVM Optimization Learning(二)
不断学习才能不断提高!
生如蝼蚁,当立鸿鹄之志,命比纸薄,应有不屈之心。
乾坤未定,你我皆是黑马,若乾坤已定,谁敢说我不能逆转乾坤?
努力吧,机会永远是留给那些有准备的人,否则,机会来了,没有实力,只能眼睁睁地看着机会溜走。