JVM
2021-06-19 00:04:26 0 举报
AI智能生成
jvm随记
作者其他创作
大纲/内容
即时编译(JIT)(有利有弊、优化1000次以上的循环)
编译简介<br>
编译器<br>
编译步骤
逃逸分析
概念 : 确定指针动态范围的方法<br>
新对象仅对该线程可见,外部线程不可见,否则则发生逃逸<br>
发生时机
发生在JIT编译阶段,收集到足够的jvm运行数据,更好的判断对象是否逃逸<br>
java逃逸分析时方法级别的,因为JIT也是方法级别的<br>
判断依据
对象被赋值堆中对象的字段(全局变量)、类的静态变量<br>
对象被传进不确定的代码中运行
优化手段
栈上分配: <br>
如果某个对象在子程序被分配,且指向该对象的指针永远不会逃逸,该对象可以分配在栈上,而不是堆上。可降低垃圾回收频率。<br>
同步锁消除:
该对象只能从一个线程访问,且该对象上的操作不需要同步<br>
分离对象/标量替换:<br>
如果该对象的访问方式不要求对象分配在连续的内存结构,那么对象的部分/全部可以不分配在内存是内存上,而是CPU寄存器上<br>
基于逃逸分析的对象分配<br>
是否逃逸?没有则分配在栈上
降低垃圾回收频率,提高性能
否则分配在堆上,是否为大对象?是则进入老年代
-XX:PretenureSizeThreshold : 大于这个值的参数直接在老年代分配
新生代是否开启线程本地分配缓存(TLAB)-占Eden 1% - (默认开启)<br>
确保多线程对象分配安全
TLAB放不下/没开,则通过CAS乐观锁分配到Eden<br>
JVM调优
性能监控
常用命令
jps:查看java进程状态<br>
jps -l :输出主类全名、是jar包则输出jar路径<br>
jps -v :查看jvm启动时显式指定的Jvm参数(jinfo -flag 查看非显式参数)
jps -m : 输出传递给main方法的参数,在嵌入式jvm上可能是null
jinfo:实时查看和设置JVM参数
查看jvm参数
jinfo PID : 显示Jvm详细参数,末尾显示默认启动参数<br>
jinfo -flag name pid :查看指定的 jvm 参数的值
jinfo -flags pid : 输出全部的参数<br>
jinfo -sysprops pid:输出当前 jvm 进行的全部的系统属性
动态修改jvm参数(并不是所有的参数都支持动态修改( 不重启虚拟机的情况下))
jinfo -flag [+|-]name pid :(布尔类型)
jinfo -flag name=value pid:(值、字符串)
jstat:查看虚拟机状态<br>
jstat –class<pid> : 监视类加载器、卸载数量、总空间及加载耗时等<br>
jstat -compiler <pid> : 显示VM实时编译的数量等信息
GC相关
jstat -gc <pid>: 可以显示gc的信息,查看gc的次数,及时间
jstat -gc 6580 250 20 : 每250ms查询一下6580进程,查询20次<br>
jstat -gccapacity <pid>:可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小
jstat -gcutil <pid>:统计gc信息
jstat -gcutil 6580 :查询6580进程各分区占比,以及MinorGC和FullGC的次数和耗时<br>
jstat -gcnew <pid>:年轻代对象的信息。
jstat -gcnewcapacity<pid>: 年轻代对象的信息及其占用量
jstat -gcold <pid>:old代对象的信息。
jstat -gcoldcapacity <pid>: old代对象的信息及其占用量。
jstat -gcpermcapacity<pid>: perm对象的信息及其占用量。
jmap:查看jvm内存快照<br>
堆中对象的统计和内存快照
生成快照信息的其他方式
应用
开发\测试环境,分析环境内存在用
生产环境
Arthas(阿尔萨斯)是阿里巴巴开源的 Java 诊断工具
jmap pid :查看进程的内存映像信息 <br>
jmap -heap pid : 打印一个堆的摘要信息,包括使用的GC算法、堆配置信息和各内存区域内存使用信息<br>
jmap -histo pid
查看线程有多少类,类有多少对象,占多少内存<br>
jmap -histo pid | head 20 : (linux)列举前20个类的对象和内存占用<br>
jmap -histo:live pid : 显示堆中对象的统计信息<br>
jmap -clstats pid :打印类加载器信息<br>
jmap -finalizerinfo pid : 打印等待终结的对象信息
jmap -dump:format=b,file=****.hprof pid : 生成堆转储快照dump文件<br>
常见分析工具
MAT(常用)
JProfiler:收费
jhat:堆转储分析工具(jdk自带)
jvisualvm:文件->装入hprof<br>
jstack:查看java堆栈信息<br>
查看线程dump文件、javacore文件(线程堆栈快照)
定位线程死锁、死循环、线程挂起等线程异常<br>
jstack检测cpu高
1. top
2. top -H -p pid 找出tid<br>
3. printf "%x\n" tid : 线程转换成16进制
4. jstack pid | grep tid(16进制) -A 30
jstack -l pid : 查看线程堆栈信息
jstack -f pid: 当’jstack [-l] pid’没有相应的时候强制打印栈信息
jstack -l pid : 长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表
jstack -m pid : 打印java和native c/c++框架的所有栈信息
拓展(getAllStackTraces)
常用可视化分析工具
JConsole(jdk自带)<br>
通过JMX的MBean(Managed Bean)对系统进 行信息收集和参数动态调整<br>
启动后会自动搜索所有JVM进程,等同于动态的jps<br>
“内存”页签相当于jstat,监视虚拟机内存变化
“线程”页签,相当于jstack检测线程状态
Java VisualVM(jdk自带)<br>
Oracle 自带主推
运行监视、故障处理、性能分析(Profiling)
Profiler页签,监测程序运行时间和内存变化(非生产)
Btrace 动态日志跟踪<br>
Arthas(阿尔萨斯)是阿里巴巴开源的 Java 诊断工具
常用命令
下载解压,启动:java -jar arthas-boot.jar,输入编号回车,即可查看启动日志<br>
dashboard:展示当前进程信息,ctrl+c 中断执行<br>
thread<br>
显示所有线程信息、对应CPU占用情况<br>
thread -pid : 查看线程在干嘛
thread -d : 监测死锁
jad 类的全路径名
显示该类的加载器和location
反编译<br>
查看代码版本
查看动态代理对象
redifine /root/***/**.class
使线上修改的代码立即生效(慎用)
原理 : classloader重新加载
trace 全限定类名方法名<br>
分析方法运行的时间、运行细节<br>
方法调用追踪
JMC:Java Mission Control(jdk自带)
持续监控,可用于生产环境
JHSDB : 基于服务性代理实现的进程外调试工具
GC日志分析工具
GCEasy<br>
GCViewer
故障定位
CPU
CPU占用高
1. top查看线程列表,占用率最高的pid
2. 使用top -hp pid 找到CPU占用最高的线程tid<br>
业务代码 : 检查代码质量:死循环、逻辑计算量大<br>
垃圾回收线程,检查是否频繁FullGC
正常FullGC,可能是服务器压力大
FullGC内存回收很少:内存泄漏
3. printf "%x\n" tid : 线程ID转换16进制(小写)
4. jstack pid | grep tid(16进制) -A 60
频繁上下文切换
Linux vmstat
vmstat 2 3 :每两秒打印一次CPU信息,打印三次<br>
CS : 表示上下文切换(Constant Switch)<br>
Linux pidstat
pidstat -w pid
针对pid进行上下文切换监控<br>
cswch/s:线程自愿上下文切换<br>
nvcswch/s:线程非自愿上下文切换(时间片用完、优先级高的线程抢走、系统中断等特殊场景)
内存
内存溢出
栈溢出(StackOverflowError)
线程请求的栈深度大于虚拟机允许的最大深度(默认1M)
检查是否有死循环、无穷的递归,非BUG可以调整栈大小(设置-Xss5m设置最大调用深度)
堆溢出(OutOfMemoryError)
原因
堆内存被用光,内存泄漏堆积<br>
虚拟机在扩展栈深度时,无法申请到足够的内存空间
OOM<br>
对象太多
修复内存泄漏,回收无用对象、调整堆大小(-Xmx)
GC回收超时<br>
-XX:MaxGCPauseMillis=300 : 修改GC的最大停止时间(个人)
-XX:-UseGCOverheadLimit :取消GC开销限制
本地内存不足
打印堆栈信息,结合使用系统工具进行分析
线程太多
减小栈大小、减小堆占用比例<br>
元空间内存不足<br>
-XX:MaxMetaspaceSize : 增加元空间内存大小
内存泄漏
原因 : 程序在申请内存后,无法释放已申请的内存空间
导致频繁GC
jstat -gc pid 1000
每秒打印一次GC信息(C:容量 | U: 已使用)(E: edon | O: old | M: MetaSpace)<br>
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT CGC CGCT GCT
jstat -gcutil pid 1000 3
每秒一次,查看最近三次GC空间占比
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
使用jmap生成内存快照信息
jmap -histo:live pid : 显示堆中对象的统计信息
jmap -dump:format=b,file=****.hprof pid : 生成堆转储快照dump文件
通过MAT的Histogram判断代码片段
线程信息
top -H -p pid
左上角显示总线程数,下面具体线程<br>
线程数
pstree -p pid | wc -l<br>
ls /proc/pid/task | wc -l
ps hH p pid | wc -l
调优阶段
上线前
根据业务QPS评估,jvm规划和jvm预调优(几台机器、机器内存、堆栈划分)<br>
上线初期
根据jvm日志解决卡顿、响应慢<br>
线上后期
解决OOM、逃逸分析、频繁fullGC<br>
调优指标
停顿时间(STW):垃圾回收中断用户线程(应用执行)的时间
降低对象创建速率
增大年轻代空间<br>
-Xmn :指定年轻代的大小
-XX:NewRatio :指定年轻代相对于老年代的大小比例
选择合适的GC算法
G1 中,可以使用系统属性 -xx:MaxGCPauseMillis来设置 GC 预期最大停顿时间
进程是否使用了 Swap 分区
分配更多的物理内存
减少在服务器上运行的进程的数量,以便它可以释放内存(RAM)
<br>减少应用程序的堆大小(我不建议这么做,因为它会导致其他副作用。不过,它可能会解决你的问题)
调整 GC 线程数
后台 I/O 活动
显示调用了 System.gc()
停顿时间(STW):垃圾回收中断用户线程(应用执行)的时间
GC时间比例:GC时间占运行时间的比例(1/(n+1))<br>
-XX:GCTimeRatio=19 ,则垃圾收集时间为1/(1+19),即5%时间用于垃圾收集
吞吐量:应用时间/(应用时间+GC时间)
1-(1/(n+1))
调优参数
标准参数(-开头)
java命令查看
java -version
java -help
非标准参数(-X开头)
java -X more 查看
-Xmn<size> 为年轻代(新生代)设置初始和最大堆大小<br>
-Xms<size> 设置初始 Java 堆大小
-Xmx<size> 设置最大 Java 堆大小
-Xss<size> 设置 Java 线程堆栈大小
非稳定参数(-XX开头)
java -XX:+PrintFlagsFinal -version 查看
-XX:+PrintGC(查看GC基本信息)
-XX:+PrintGCDetails(查看GC详细信息)
-XX:+PrintHeapAtGC(查看GC各域变化情况)
调优案例(亿级流量)
如何设置堆大小?
几乎不发生FullGC?<br>
线程死锁
死锁条件
互斥
请求和保持等待
不可剥夺
环路等待
预防死锁
破坏死锁条件
指定加锁顺序、设置超时时间<br>
银行家算法
保证分配资源后不进入不安全状态<br>
死锁检测
jstack
Jconsole<br>
组成结构
加载子系统(按需加载)
类对象
对象内存结构
对象头(Header)<br>
Mark Word<br>
哈希吗<br>
GC分代年龄
锁状态标志<br>
线程持有锁<br>
偏向锁ID<br>
偏向锁时间戳 等<br>
占用8字节
类型指针
对象所属类的元信息在方法区的内存地址<br>
开启指针压缩
4字节
不开启指针压缩
8字节
数组长度
若对象为数组,jvm无法计算该对象大小,需要在对象头记录数组长度<br>
占4字节
关闭指针压缩会涉及头部填充<br>
实例数据(instance data)
对象的有效信息(对象中所定义的各类型字段内容)
对齐填充(padding)
对象大小必须为8字节的整数倍,填充保证知字节对齐(非必须)<br>
对象创建过程
1. jvm遇见new时,先在常量池检测,类有没有被加载,否则先加载
2. 检查通过后为对象分配内存<br>
指针碰转(堆内存规整)
使用的一边,空闲的一边;分配时指针往空闲内存移动一个对象大小<br>
空闲列表(堆内存不规整)
jvm列表记录哪些使用、哪些未使用,找出空闲内存分配该对象,并记录在列表中
并发因素
CAS同步<br>
CAS+重试,保证原子性
本地线程栈分配缓存(TLAB)(堆中)<br>
每个线程在Java堆中预先分配一小块内存,哪个线程需要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了才需要同步锁定(是否开启 TLAB 可通过 -XX:+/-UseTLAB参数来设定)<br>
3. 内存中进行初始化,初始化为默认值<br>
4. 对对象进行必要的设置<br>
设置对象头<br>
哈希吗、元数据、分代年龄、是否偏向锁等
5. 执行 init()
对象的访问定位
使用句柄
堆中划分句柄池,reference 就是存储对象的句柄地址,指向对象的引用<br>
特点
reference中存放的是句柄池地址,对象地址改变只会影响句柄池数据,不会直接影响reference数据<br>
直接指针
reference中直接存储对象地址,少去句柄池赚差价<br>
特点
访问速度快,节省一次指针定位时间<br>
加载流程
加载
加载class文件进内存
连接
验证
确保类加载的正确性(文件格式、元数据、字节码、符号引用...)<br>
准备
给静态变量分配内存,并赋默认值<br>
解析
将符号引用转化为直接引用
初始化
将静态变量由默认值改为赋予实际值
使用
卸载
类加载器
种类
1. 引导类加载器/Bootstrap Classloader
加载jre/lib/下的核心类库(jvm自己的类),获取该加载器时为null,因为非Java实现
2. 扩展类加载器/Exection Classloader
加载jre/lib/ext下的扩展jar<br>
3. 系统类加载器/Application Classloader
classpath下的类(常用:90%以上)
4. 自定义类加载器/Custom Classloader
加载自定义路径下的类
作用<br>
将字节码加载到内存<br>
确定类的唯一性
提供隔离性<br>
为中间件开发者提供便利(Tomcat)
加载机制
双亲委派
概述
依赖父加载器进行加载,父加载器完不成,自己再来加载
作用
安全,防止核心类库被修改<br>
避免重复加载,父加载器已经加载,本身没必要再加载<br>
加载顺序(4 -> 3 -> 2 -> 1)
打破双亲委派机制(Tomcat)
作用
防止类冲突,资源隔离<br>
如何打破
用户自定义类加载器,重写加载逻辑
使用的产品
Tomcat、JDBC、热部署框架<br>
jvm指令集
javap -c
运行时数据
堆(共享)
作用
存放对象实例(存放运行时常量+新生代+老年代)
构成
字符串常量池(java8迁移)
底层HashTable
key
字符串的hashcode值
算出值对应的index
value
字符串的引用<br>
实例成员变量
非static修饰的变量,属于对象
线程分配缓冲区(Thread Local Allocation Buffer)
用于与线程的本地缓冲区
新生代(1/3)
eden(占80%)
s0(占10%)
s1(占10%)
老年代(2/3)
异常
OOM
不断的new对象
方法区/元空间(共享)
作用
存放类信息、常量、静态变量和方法等类的加载信息(jdk8改为本地内存)<br>
构成
方法区
被虚拟机加载的类信息、常量、静态变量、即时编译器编译之后的代码等数据(class文件信息)<br>
常量池
静态常量池(class静态描述信息)<br>
编译生成的字面量和符号引用(类、接口、方法、字段的class静态描述信息)<br>
运行时常量池(加载的class数据)<br>
从静态常量池加载的数据
类、接口、方法、字段的描述信息<br>
final修饰的变量(常量,属于类)(方法外)(方法内属于栈)<br>
基本类型的包装类的值(-128~127)
字符串常量池(使用时)
被引用的字符串常量会被加载进来
静态(static)的变量(属于类与对象无关)
字符串常量池(java8开始移到堆上)
异常
OOM
加载的类太多
加载了重复类
large class
加载器泄漏
栈(FIFO)(私有)
作用
描述java方法执行的内存模型,随线程消亡无需回收<br>
构成
局部变量(方法内声明的变量)表<br>
存放java方法中的局部变量(8种基本数据类型)<br>
若变量为对象,则为对象地址<br>
final修饰的变量(方法内)(方法外属于方法区)
操作数栈<br>
对象做逻辑运算的临时空间<br>
动态链接<br>
符号引用转化为直接引用
方法出口<br>
存储运行方法前的执行位置<br>
异常
栈空间不可扩展,且栈帧超过了栈的最大深度
栈溢出
栈空间可扩展,但申请不到足够的内存<br>
内存溢出
本地方法栈(私有)
与虚拟机栈类似
虚拟机栈为java方法服务
本地方法栈为native方法服务
程序计数器(私有)
记录代码执行行数
线程切换时保存执行状态
唯一不会OOM,没有垃圾回收,跟随线程消亡
执行java代码时,记录执行虚拟机字节码的指令地址<br>
执行本地方法(native),则为空
内存结构图
执行引擎
执行字节码的方式
字节码解析器
java字节码 -> C/C++ -> 机器码
模板解释器
java字节码 -> 机器码
底层原理
1. 将new()的硬编码拿过来<br>
2. 申请内存(读、写、执行)
3. 硬编码写入该内存
4. 声明函数指针指向该内存
5. 通过该函数调用该内存
执行字节码的模式<br>
Xint:存字节码解析器运行<br>
Xcomp:存模板解析器运行<br>
Xmixed:混合模式(默认)<br>
java -version
本地库接(JNI)}
这些程序和类库可以是其它语言编写的,比如C、C++或者汇编语言。
垃圾回收
垃圾的位置
堆、方法区<br>
程序计数器、虚拟机栈、本地方法栈:(这些用完就释放,不存在垃圾回收问题)<br>
GC
Minor GC发生在Eden区;
<span style="color: rgb(0, 0, 0); font-family: "Helvetica Neue", Helvetica, Verdana, Arial, sans-serif; font-size: 14px;">Young GC发生在Eden、S0、S1区;</span>
GC对应分区
<span style="color: rgb(0, 0, 0); font-family: "Helvetica Neue", Helvetica, Verdana, Arial, sans-serif; font-size: 14px;">Major GC发生在Old区</span>
什么是垃圾
算法(存在STW)
引用计数法
原理:对象中添加引用计数器,有引用+1,引用失效-1,0为垃圾<br>
缺点:存在循环引用问题
可达性分析
原理<br>
由起始对象节点(GC Roots)开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。<br>
GC Roots
虚拟机栈(栈帧中的本地变量表)中引用的对象
本地方法栈中(Native方法)引用的对象
方法区中静态属性和常量所引用的对象<br>
跨代引用的对象<br>
和GCRoot 所属同一tablecard的对象<br>
对象引用类型
强引用<br>
只要强引用还在,垃圾回收器就不会回收<br>
软引用
只有在内存不足时,OOM之前才会被回收
做缓存
弱引用<br>
非必须对象,只要GC就回收<br>
ThreadLocal对象使用弱引用防止内存泄漏<br>
虚引用
相当于没引用,被回收时会收到通知<br>
背景:GC只能管jvm,管不了直接内存<br>
作用:管理直接内存,被回收前释放直接内存<br>
原理:虚引用借助队列实现,虚引用被回收前会向队列写数据,监听队列即可接收通知<br>
应用:netty的零拷贝
缺点<br>
背景:单收集线程工作不会有问题,收集线程和用户线程同时工作,会出现分析错乱<br>
原因
1. 赋值器插入黑色到白色的新引用<br>
2. 赋值器删除灰色到白的直接或者间接引用<br>
后果<br>
1. 死的对象标记为活的(可容忍,下次再回收这些浮动垃圾)
2. 活的对象标记为死的(会导致程序错误)
解决方案
CMS(增量更新)
原理:打破场景1,将所有黑色对象都标记为灰色,再扫一遍<br>
缺点:<br>
并发标记会存在漏标
解决方案:remark(从头重新标记)<br>
导致STW时间很长
使用标记清除算法会产生大量内存碎片<br>
大对象没有连续内存分配时,触发担保机制,导致和长时间的STW
G1(原始快照STAB)
原理:打破场景2,(按照当时快照场景处理)将所有删除的灰色到白色的引用记录下来,并发扫描结束后,以灰色对象为根再扫描一遍<br>
优缺点
与CMS对比
采用标记整理算法不会产生大量内存碎片
并发标记结束后,只扫描灰色对象,STW短
STW可控,实现高吞吐量的前提下,低延迟回收垃圾<br>
充分利用CPU,利用多核优势减少STW(会竞争CPU资源)<br>
注意 : 最终判定
首次标记筛查<br>
是否有必要执行finalize()方法?没必要直接回收
直接回收(没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过)
第二次标记<br>
放入队列F-Queue,Finalizer线程去执行回收
再次标记(<b>起死回生</b>)
重新与引用链上的任何的一个对象建立关联。对象起死回生;譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合;
三色标记
白色
垃圾回收器没访问过(新对象)
访问过仍为白色<br>
对象不可达
灰色
已经被垃圾回收器扫描(引用还没扫描完)
黑色
已经被垃圾回收器扫描(安全存活对象(所有引用均已扫描))
指向存活对象的其他对象均为黑色,不可能为白色<br>
垃圾回收算法
标记清除
1. 标记(mark)
2. 清除(sweep)
优缺点<br>
不浪费内存(相对复制算法)<br>
会产生碎片,效率低(要扫描两次)<br>
标记整理
1. 标记(mark)
2. 清除(sweep)
3. 整理存活对象,顺序排列<br>
优缺点<br>
避免了碎片和空间占用<br>
比复制算法效率低(维护链表使存活对象连续)
复制算法
1. 存在两个相等区域,只使用一个区域<br>
2. 将存活对象来回copy
优缺点
存活对象少时,效率高
存活对象多时,效率低
内存占用大(来回复制)
分代回收
新生代
复制算法
8:1:1 eden空间不足时,触发youngGC<br>
内存分配策略
大对象直接进老年代
减少大对象复制时内存开销
长期存活对象进入老年代<br>
每存活一次年龄+1,达到时15岁进入老年代
动态年龄判断尽早进入老年代
young GC后,检测到可能长期存活的对象直接进入老年代
老年代空间担保机制<br>
young GC前风险判断,允许担保失败?否则Full GC<br>
老年代
标记清除
标记整理
youngGC/fullGC
注意:内存回收
方法区(元空间)
元空间不在虚拟机内存中,使用直接内存<br>
回收废弃常量和无用类
Full GC<br>
垃圾收集器
分代模型
新生代
Serial(串行)<br>
单线程、简单高效
ParNew(并行)<br>
Serial的多线程版本,线程数与CPU核数相同,默认与CMS配合的新生代收集器
Parallel Scavenge(并行)
不支持CMS,以吞吐量优先的多线程收集器,高效利用CPU
老年代
Serial Old(串行)
Serial的老年版本,CMS的后备方案
Parallel Old(并行)
Parallel Scavenge 的老年代版本,吞吐量优先
CMS(并发)(增量更新)
并发收集、低停顿(STW)、注重用户体验
流程
初始标记
标记GCRoot关联的对象,STW短
并发标记
遍历GCRoot直接关联的对象,耗时久
重新标记<br>
修正并发标记期间的错误,STW略长
并发清除
与用户线程并发,清除垃圾对象
缺点
并发阶段虽不会昂用户线程暂停,但是会与用户线程抢占CPU资源,降低吞吐量<br>
用户线程和垃圾回收线程并发,期间会产生新垃圾,或者触发担保机制,造成长时间停顿<br>
基于标记清除算法会产生大量内存碎片,没有连续空间存放大对象时,会导致提前FullGc<br>
组合使用
jdk8默认(Parallel Scavenge + Parallel Old )<br>
分区模型
G1(并发)(快照模式)(逻辑分代、物理不分区)<br>
引入分区的概念、弱化分代概念
Region:将java堆划分为对个连续大小相等的独立区域<br>
Humongous :专门用来存大对象(超过了一个Region容量一半)
流程<br>
初始标记
标记GCRoot关联到的对象,暂停用户线程,STW时间较短
并发标记
可达性分析找出要回收的对象,记录期间引用变动,耗时较长
最终标记
重新标记并发标记期间引用变动对象,暂停用户线程STW<br>
筛选回收
对个Region的灰色价值和成本排序,根据用户期望的STW制定回收计划,暂停用户线程,多线程回收STW<br>
总结
除并发标记外其他都要STW,延迟可控的情况下提高吞吐量
YoungGC
MixedGC
优缺点
小内存场景CMS占优,大内存G1占优
采用标记整理,不产生垃圾碎片<br>
利用多核优势,从分利用CPU,缩短STW<br>
定制化处理,停顿时间可控,保证吞吐量和减小STW<br>
内存占用高,执行负载大
维护记忆集(记忆集会记录下别的Region 指向自己的指针,并标记这些指针分别在哪些卡页的范围)
问题点
G1 STW时间怎么可控?
Garbage First, 后台维护一个优先级列表,规定时间内,优先回收最大价值的Region<br>
Region是垃圾回收的最小单元,根据计划每次回收Region的整数倍,避免全局回收
G1停顿模型怎么预测?<br>
通过衰减平均值(decaying average)预算模型实现,表示最近的平均值
region 的统计状态值越新,越能体现其回收价值<br>
G1对象夸Region引用怎么解决?
使用记忆集,避免全局扫描GCRoot
并发标记阶段,怎么保证用户线程和回收线程互不干扰?
CMS通过增量更新
G1通过快照模式(SATB)
ZGC(JDK11)<br>
Shenandoash(JDK12)
其他新版本GC
epsilon
PGC、C4、OpenJ9等<br>
JVM调优
Parallel常用参数
CMS常用参数
G1常用参数
垃圾收集日志
默认级别(低->高)
Trace、Debug、Info、Warning、Error、Off<br>
默认Info
GC命令
GC日志信息<br>
查看GC基本信息<br>
-XX:+PrintGC(JDK9之前)
-Xlog:gc(JDK9之后)<br>
查看GC详细信息<br>
-XX:+PrintGCDetails(JDK9之前)
-Xlog:gc*(JDK9之后)
查看GC前后各区域空间变化<br>
-XX:+PrintHeapAtGC(JDK9之前)
-Xlog:gc+heap=debug(JDK9之后)
查看GC期间用户线程的停顿时间和并发时间<br>
-XX:+PrintGCApplicationConcurrentTime和-XX:+PrintGCApplicationStoppedTime(JDK9之前)
-Xlog:safepoint(JDK9之后)
常看GC后剩余对象的年龄分布
-XX:+PrintTenuringDistribution(JDK9之前)
-Xlog:gc+age=trace(JDK9之后)<br>
JVM参数设置
堆内存新生代Eden和Survivor比例<br>
-XX:SurvivorRatio=8
内存溢出打印
-XX:+HeapDumpOnOutOfMemoryError
堆转储快照存储位置
-XX:HeapDumpPath=${目录}
元空间
设置元空间最大值
-XX:MaxMetaspaceSize(默认-1,即不受限制,只受限于本地内存大小)
指定元空间的初始空间大小
-XX:MetaspaceSize
控制GC后最小的元空间剩余容量的百分比
-XX:MinMetaspaceFreeRatio
控制GC最大的元空间剩余容量的百分比
指定直接内存的大小
-XX:MaxDirectMemorySize
GC日志分析工具
GCEasy<br>
GCViewer
0 条评论
下一页