目录
一、GC介绍
1.什么是垃圾
2.如何定位垃圾
3.常见的垃圾回收算法
4.JVM内存分代模型(用于分代垃圾回收算法)
5.常用的垃圾收集器
Serial
Serial Old
Parsllel Scavenge
Parallel Old
CMS
ParNew
G1
二、调用工具
1.常见调优命令
jps:虚拟机进程状况工具
jstat(JVM statistice Monitoring Tool):虚拟机统计信息监视工具
jinfo(Configuration Info for Java):java配置信息工具
jmap(Memory Map for Java):java内存影响工具
jstack(java堆栈跟踪工具)
2.可视化工具
JConsole:java监视与管理控制台
VisualVM是功能强大的运行监视和故障处理软件之一。可以安装很多插件,集成很多功能。
arthas:是 Alibaba 开源的 Java 诊断工具,非常好用
三、JVM调优策略
1.选择合适的垃圾回收器
2.调整内存大小
3.调整大对象的标准
4.调整GC的触发时机
5.更多调优策略
垃圾的定义:没有任何引用指向的一个对象或者多个对象(循环引用)
引用计数(ReferenceCount)内存上有一块空间记录被引用的次数
根可达算法(RootSearching)
标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)
拷贝算法 (copying) - 没有碎片,浪费内存空间
标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)
Serial是历史比较久远、方式比较直接的收集器。它是一个单线程工作的收集器,在Serial工作期间要停止所有的业务线程(STW),等待Serial工作完毕,业务线程再开始工作。
使用标记-复制算法收集新生代垃圾。
Serial Old是Serial 收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
同样是在工作期间,停止所有业务线程。
Parsllel Scavenge是新生代的垃圾收集器,采用标记-复制算法。但是他是一个多线程的,其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是提高吞吐量(吞吐量 = 运行用户程序的时间 / (运行用户程序的时间 + 垃圾收集的时间))。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,支持多线程并行收集。采用标记-整理算法实现。
Parsllel Scavenge与Parallel Old是JDK1.8版本默认的垃圾收集器
CMS(Concurrent Mark Sweep),收集器几乎占据着 JVM 老年代收集器的半壁江山,它划时代的意义就在于垃圾回收线程几乎能做到与用户线程同时工作。
使用标记-清除算法收集老年代垃圾。
工作流程主要有如下 4 个步骤:
在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。
工作在新生代,本质为Serial收集器的多线程版本,采用“复制算法”;只有它能与CMS收集器配合工作
该收集器可以算得上是垃圾收集器技术发展历史上里程碑式的成果。我们之前聊的垃圾收集器,垃圾收集的目标要么是针对整个老年代(Major GC),要么就是针对整个新生代(Minor GC),要么就好似整个Java堆进行回收(Full GC)。但是G1则提出了另一种内存布局形式–基于Region的内存布局形式。当然这种内存布局形式也是基于分代理论设计的,但是它不再坚持固定大小以及固定数量的分代区域划分,而是将连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以扮演新生代的Eden空间,Survivor空间,或者老年代空间。当然对于一个超过了Region容量一半的对象,会被判定为大对象,将会用特殊的Humongous来进行存储,如果对象过大则会存储在连续的Humongous中,G1的大多数行为会将Humongous区域当作老年代来看待。
使用复制 + 标记 - 整理算法收集新生代和老年代垃圾。
G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。
调优第一种:在业务正常使用的情况下,重启(做好负载)
调优第二种:换垃圾回收器
调优第三种:看日志、使用工具、排查问题、优化逻辑
选项 | 作用 |
-q | 只输出LVMID,省略主类名称 |
-m | 输出虚拟机进程启动是传递给主类main()函数的参数 |
-l | 输出主类名 |
-v | 输出虚拟机进程启动时JVM参数 |
[admin@d-metaverse ~]$ jps -l
8611 /home/admin/metaverse-backend-dev/metaverse-provider-cim-1.0-SNAPSHOT.jar
9572 /home/admin/metaverse-backend-dev/metaverse-provider-login-1.0-SNAPSHOT.jar
1797 /home/admin/metaverse-backend-dev/metaverse-provider-stepbar-1.0-SNAPSHOT.jar
8421 /home/admin/metaverse-backend-dev/metaverse-gateway-1.0-SNAPSHOT.jar
8268 /home/admin/metaverse-backend-dev/metaverse-provider-timing-1.0-SNAPSHOT.jar
9933 /home/admin/metaverse-backend-dev/metaverse-provider-course-1.0-SNAPSHOT.jar
8879 /home/admin/metaverse-backend-dev/metaverse-provider-activity-1.0-SNAPSHOT.jar
9746 /home/admin/metaverse-backend-dev/metaverse-provider-subassembly-1.0-SNAPSHOT.jar
10258 /home/admin/metaverse-backend-dev/metaverse-provider-pushclass-1.0-SNAPSHOT.jar
10067 /home/admin/metaverse-backend-dev/metaverse-provider-synchronization-1.0-SNAPSHOT.jar
9018 /home/admin/metaverse-backend-dev/metaverse-provider-fileupload-1.0-SNAPSHOT.jar
9338 /home/admin/metaverse-backend-dev/metaverse-provider-chapter-1.0-SNAPSHOT.jar
11771 jdk.jcmd/sun.tools.jps.Jps
11518 /home/admin/metaverse-backend-dev/metaverse-provider-class-1.0-SNAPSHOT.jar
选项 | 作用 |
-class | 监视类加载、下载数量、总空间等 |
-gcutil | 与-gc 输出一直,主要关注已使用空间占总空间的占比 |
-gc | 监视堆状况,包括Eden、Survivor、老年代、永久代等 |
[admin@t-points ~]$ jstat -class 1351
Loaded Bytes Unloaded Bytes Time
13693 25173.1 0 0.0 74.57
[admin@t-points ~]$ jstat -gc 1351
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
7168.0 8704.0 6240.0 0.0 157184.0 78732.0 349696.0 41656.1 76720.0 73530.4 9904.0 9255.2 28 1.486 3 1.485 2.971
[admin@t-points ~]$ jstat -gcutil 1351
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
87.05 0.00 50.09 11.91 95.84 93.45 28 1.486 3 1.485 2.971
选项 | 作用 |
-sysprops | 获取环境变量 |
[admin@t-points ~]$ jinfo -sysprops 1351
Attaching to process ID 1351, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.251-b08
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.251-b08
sun.boot.library.path = /usr/java/jdk1.8.0_251-amd64/jre/lib/amd64
java.protocol.handler.pkgs = org.springframework.boot.loader
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.country = US
user.dir = /
java.vm.specification.name = Java Virtual Machine Specification
PID = 1351
java.runtime.version = 1.8.0_251-b08
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /usr/java/jdk1.8.0_251-amd64/jre/lib/endorsed
line.separator =
java.io.tmpdir = /tmp
java.vm.specification.vendor = Oracle Corporation
os.name = Linux
sun.jnu.encoding = UTF-8
jetty.git.hash = 27208684755d94a92186989f695db2d7b21ebc51
java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
spring.beaninfo.ignore = true
java.specification.name = Java Platform API Specification
java.class.version = 52.0
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
os.version = 3.10.0-1062.18.1.el7.x86_64
user.home = /home/admin
user.timezone = Asia/Shanghai
catalina.useNaming = false
java.awt.printerjob = sun.print.PSPrinterJob
file.encoding = UTF-8
@appId = integral_mobile
java.specification.version = 1.8
catalina.home = /tmp/tomcat.7673651076886970121.8118
user.name = admin
java.class.path = /home/admin/apps/integral_mobile-0.0.1-SNAPSHOT.jar
java.vm.specification.version = 1.8
sun.arch.data.model = 64
sun.java.command = /home/admin/apps/integral_mobile-0.0.1-SNAPSHOT.jar
java.home = /usr/java/jdk1.8.0_251-amd64/jre
user.language = en
java.specification.vendor = Oracle Corporation
awt.toolkit = sun.awt.X11.XToolkit
java.vm.info = mixed mode
java.version = 1.8.0_251
java.ext.dirs = /usr/java/jdk1.8.0_251-amd64/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path = /usr/java/jdk1.8.0_251-amd64/jre/lib/resources.jar:/usr/java/jdk1.8.0_251-amd64/jre/lib/rt.jar:/usr/java/jdk1.8.0_251-amd64/jre/lib/sunrsasign.jar:/usr/java/jdk1.8.0_251-amd64/jre/lib/jsse.jar:/usr/java/jdk1.8.0_251-amd64/jre/lib/jce.jar:/usr/java/jdk1.8.0_251-amd64/jre/lib/charsets.jar:/usr/java/jdk1.8.0_251-amd64/jre/lib/jfr.jar:/usr/java/jdk1.8.0_251-amd64/jre/classes
java.awt.headless = true
java.vendor = Oracle Corporation
catalina.base = /tmp/tomcat.7673651076886970121.8118
file.separator = /
java.vendor.url.bug = http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
sun.cpu.isalist =
选项 | 作用 |
-dump | 生成Java堆转储快照 |
-heap | 显示Java堆详细信息、使用哪种回收器、参数配置等 |
-histo | 显示堆中对象统计信息、包括类、对象数量、合计容量 |
-F | 强制生成dump快照 |
[admin@t-points ~]$ jmap -histo 1351 |head -20
num #instances #bytes class name
----------------------------------------------
1: 333202 34434976 [C
2: 49888 29245832 [B
3: 19893 13926960 [I
4: 244165 5859960 java.lang.String
5: 48470 4265360 java.lang.reflect.Method
6: 41238 4143776 [Ljava.lang.Object;
7: 73412 2936480 java.util.HashMap$KeyIterator
8: 73948 2366336 java.util.concurrent.ConcurrentHashMap$Node
9: 52068 2082720 java.util.LinkedHashMap$Entry
10: 14602 1618768 java.lang.Class
11: 20869 1591656 [Ljava.util.HashMap$Node;
12: 24694 1382864 java.util.LinkedHashMap
13: 19429 1243456 java.net.URL
14: 24069 1155312 org.springframework.util.ConcurrentReferenceHashMap$SoftEntryReference
15: 30388 972416 java.util.HashMap$Node
16: 41523 940672 [Ljava.lang.Class;
17: 11083 886640 org.springframework.boot.loader.jar.JarURLConnection
[admin@t-points ~]$ jmap -heap 1351
Attaching to process ID 1351, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.251-b08
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 536870912 (512.0MB)
NewSize = 178782208 (170.5MB)
MaxNewSize = 178782208 (170.5MB)
OldSize = 358088704 (341.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 160956416 (153.5MB)
used = 85097896 (81.15567779541016MB)
free = 75858520 (72.34432220458984MB)
52.870148400918666% used
From Space:
capacity = 7340032 (7.0MB)
used = 6389792 (6.093780517578125MB)
free = 950240 (0.906219482421875MB)
87.05400739397321% used
To Space:
capacity = 8912896 (8.5MB)
used = 0 (0.0MB)
free = 8912896 (8.5MB)
0.0% used
PS Old Generation
capacity = 358088704 (341.5MB)
used = 42655824 (40.67976379394531MB)
free = 315432880 (300.8202362060547MB)
11.91208310218018% used
选项 | 作用 |
-F | 当正常输出的请求不响应是,强制输出线程堆栈 |
-l | 处堆栈外,显示关于锁的附加信息 |
[admin@t-points ~]$ jstack -l 1351
2022-08-08 20:05:48
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.251-b08 mixed mode):
"Attach Listener" #108 daemon prio=9 os_prio=0 tid=0x00007f7b40001800 nid=0x1c3f waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"qtp1002276800-107" #107 prio=5 os_prio=0 tid=0x00007f7b3c47a800 nid=0x1951 waiting on condition [0x00007f7b1dbe2000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000e269ded8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:392)
at org.eclipse.jetty.util.thread.QueuedThreadPool.idleJobPoll(QueuedThreadPool.java:656)
at org.eclipse.jetty.util.thread.QueuedThreadPool.access$800(QueuedThreadPool.java:49)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:720)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
JConsole是一款基于JMX的可视化监视、管理工具。
JConsole在javahome/bin目录下,jconsole.exe
等等
VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
安装使用:官网地址:简介 | arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
启动
[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki: https://arthas.aliyun.com/doc
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
查看dashboard
$ dashboard
ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON
17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false
27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true
11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true
9 Attach Listener system 9 RUNNAB 0 0:0 false true
3 Finalizer system 8 WAITIN 0 0:0 false true
2 Reference Handler system 10 WAITIN 0 0:0 false true
4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true
26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true
13 job-timeout system 9 TIMED_ 0 0:0 false true
1 main main 5 TIMED_ 0 0:0 false false
14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false
18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false
23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false
15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false
Memory used total max usage GC
heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166
ps_survivor_space 4M 5M 5M s)
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0
nonheap 20M 23M -1 gc.ps_marksweep.time( 0
code_cache 3M 5M 240M 1.32% ms)
Runtime
os.name Mac OS X
os.version 10.13.4
java.version 1.8.0_162
java.home /Library/Java/JavaVir
tualMachines/jdk1.8.0
_162.jdk/Contents/Hom
e/jre
- CPU单核,那么毫无疑问Serial 垃圾收集器是你唯一的选择。
- CPU多核,关注吞吐量 ,那么选择PS+PO组合。
- CPU多核,关注用户停顿时间,JDK版本1.6或者1.7,那么选择CMS。
- CPU多核,关注用户停顿时间,JDK1.8及以上,JVM可用内存6G以上,那么选择G1。
//设置Serial垃圾收集器(新生代)
开启:-XX:+UseSerialGC
//设置PS+PO,新生代使用功能Parallel Scavenge 老年代将会使用Parallel Old收集器
开启 -XX:+UseParallelOldGC
//CMS垃圾收集器(老年代)
开启 -XX:+UseConcMarkSweepGC
//设置G1垃圾收集器
开启 -XX:+UseG1GC
现象:垃圾收集频率非常频繁。
原因:如果内存太小,就会导致频繁的需要进行垃圾收集才能释放出足够的空间来创建新的对象,所以增加堆内存大小的效果是非常显而易见的。
注意:如果垃圾收集次数非常频繁,但是每次能回收的对象非常少,那么这个时候并非内存太小,而可能是内存泄露导致对象无法回收,从而造成频繁GC。
参数配置:
//设置堆初始值
指令1:-Xms2g
指令2:-XX:InitialHeapSize=2048m
//设置堆区最大值
指令1:`-Xmx2g`
指令2: -XX:MaxHeapSize=2048m
//新生代内存配置
指令1:-Xmn512m
指令2:-XX:MaxNewSize=512m
现象:老年代频繁GC,每次回收的对象很多,而且单个对象的体积都比较大。
原因:如果大量的大对象直接分配到老年代,导致老年代容易被填满而造成频繁GC,可设置对象直接进入老年代的标准。
注意:这些大对象进入新生代后可能会使新生代的GC频率和时间增加。
配置参数:
//新生代可容纳的最大对象,大于则直接会分配到老年代,0代表没有限制。
-XX:PretenureSizeThreshold=1000000
现象:CMS,G1 经常 Full GC,程序卡顿严重。
原因:G1和CMS 部分GC阶段是并发进行的,业务线程和垃圾收集线程一起工作,也就说明垃圾收集的过程中业务线程会生成新的对象,所以在GC的时候需要预留一部分内存空间来容纳新产生的对象,如果这个时候内存空间不足以容纳新产生的对象,那么JVM就会停止并发收集暂停所有业务线程(STW)来保证垃圾收集的正常运行。这个时候可以调整GC触发的时机(比如在老年代占用60%就触发GC),这样就可以预留足够的空间来让业务线程创建的对象有足够的空间分配。
注意:提早触发GC会增加老年代GC的频率。
配置参数:
//使用多少比例的老年代后开始CMS收集,默认是68%,如果频繁发生SerialOld卡顿,应该调小
-XX:CMSInitiatingOccupancyFraction
//G1混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%
-XX:G1MixedGCLiveThresholdPercent=65
【JVM系列5】JVM调优实例 - 知乎
JVM实战调优 - zydbky - 博客园
JVM参数调优总结 -Xms -Xmx -Xmn -Xss_jakeswang的博客-CSDN博客_jvm xms xmx 参数详解
看书:深入理解Java虚拟机