JVM调优理论与实践最佳结合

x33g5p2x  于2022-08-17 转载在 Java  
字(13.0k)|赞(0)|评价(0)|浏览(419)

一、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.更多调优策略

一、GC介绍

1.什么是垃圾

垃圾的定义:没有任何引用指向的一个对象或者多个对象(循环引用)

2.如何定位垃圾

引用计数(ReferenceCount)内存上有一块空间记录被引用的次数

根可达算法(RootSearching)

3.常见的垃圾回收算法

标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)

拷贝算法 (copying) - 没有碎片,浪费内存空间

标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)

4.JVM内存分代模型(用于分代垃圾回收算法)

5.常用的垃圾收集器

Serial

Serial是历史比较久远、方式比较直接的收集器。它是一个单线程工作的收集器,在Serial工作期间要停止所有的业务线程(STW),等待Serial工作完毕,业务线程再开始工作。

使用标记-复制算法收集新生代垃圾。

Serial Old

Serial Old是Serial 收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。

同样是在工作期间,停止所有业务线程。

Parsllel Scavenge

**Parsllel Scavenge是新生代的垃圾收集器,采用标记-复制算法。但是他是一个多线程的,**其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是提高吞吐量(吞吐量 = 运行用户程序的时间 / (运行用户程序的时间 + 垃圾收集的时间))。

Parallel Old

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,支持多线程并行收集。采用标记-整理算法实现。

Parsllel Scavenge与Parallel Old是JDK1.8版本默认的垃圾收集器

CMS

CMS(Concurrent Mark Sweep),收集器几乎占据着 JVM 老年代收集器的半壁江山,它划时代的意义就在于垃圾回收线程几乎能做到与用户线程同时工作。

使用标记-清除算法收集老年代垃圾。

工作流程主要有如下 4 个步骤:

  • 初始标记: 仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿(Stop-the-world)
  • 并发标记: 进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿
  • 重新标记: 为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿(Stop-the-world)
  • 并发清除: 清理垃圾,不需要停顿

在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿

ParNew

工作在新生代,本质为Serial收集器的多线程版本,采用“复制算法”;只有它能与CMS收集器配合工作

G1

该收集器可以算得上是垃圾收集器技术发展历史上里程碑式的成果。我们之前聊的垃圾收集器,垃圾收集的目标要么是针对整个老年代(Major GC),要么就是针对整个新生代(Minor GC),要么就好似整个Java堆进行回收(Full GC)。但是G1则提出了另一种内存布局形式–基于Region的内存布局形式。当然这种内存布局形式也是基于分代理论设计的,但是它不再坚持固定大小以及固定数量的分代区域划分,而是将连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以扮演新生代的Eden空间,Survivor空间,或者老年代空间。当然对于一个超过了Region容量一半的对象,会被判定为大对象,将会用特殊的Humongous来进行存储,如果对象过大则会存储在连续的Humongous中,G1的大多数行为会将Humongous区域当作老年代来看待。

使用复制 + 标记 - 整理算法收集新生代和老年代垃圾。

G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。

二、调用工具

调优第一种:在业务正常使用的情况下,重启(做好负载)

调优第二种:换垃圾回收器

调优第三种:看日志、使用工具、排查问题、优化逻辑

1.常见调优命令

jps:虚拟机进程状况工具

| 选项 | 作用 |
| -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

jstat(JVM statistice Monitoring Tool):虚拟机统计信息监视工具

| 选项 | 作用 |
| -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

jinfo(Configuration Info for Java):java配置信息工具

| 选项 | 作用 |
| -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 =

jmap(Memory Map for Java):java内存影响工具

| 选项 | <br>作用<br> |
| -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

jstack(java堆栈跟踪工具)

| 选项         | 作用 |
| -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

2.可视化工具

JConsole:java监视与管理控制台

JConsole是一款基于JMX的可视化监视、管理工具。

JConsole在javahome/bin目录下,jconsole.exe

等等

VisualVM是功能强大的运行监视和故障处理软件之一。可以安装很多插件,集成很多功能。

VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。

 arthas:是 Alibaba 开源的 Java 诊断工具,非常好用

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

三、JVM调优策略

1.选择合适的垃圾回收器

  • 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

2.调整内存大小

现象:垃圾收集频率非常频繁。

原因:如果内存太小,就会导致频繁的需要进行垃圾收集才能释放出足够的空间来创建新的对象,所以增加堆内存大小的效果是非常显而易见的。

注意:如果垃圾收集次数非常频繁,但是每次能回收的对象非常少,那么这个时候并非内存太小,而可能是内存泄露导致对象无法回收,从而造成频繁GC。

参数配置:

//设置堆初始值
 指令1:-Xms2g
 指令2:-XX:InitialHeapSize=2048m
 ​
 //设置堆区最大值
 指令1:`-Xmx2g` 
 指令2: -XX:MaxHeapSize=2048m
 ​
 //新生代内存配置
 指令1:-Xmn512m
 指令2:-XX:MaxNewSize=512m

3.调整大对象的标准

现象:老年代频繁GC,每次回收的对象很多,而且单个对象的体积都比较大。

原因:如果大量的大对象直接分配到老年代,导致老年代容易被填满而造成频繁GC,可设置对象直接进入老年代的标准。

注意:这些大对象进入新生代后可能会使新生代的GC频率和时间增加。

配置参数:

//新生代可容纳的最大对象,大于则直接会分配到老年代,0代表没有限制。
  -XX:PretenureSizeThreshold=1000000

4.调整GC的触发时机

现象: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

5.更多调优策略

【JVM系列5】JVM调优实例 - 知乎

JVM实战调优 - zydbky - 博客园

JVM参数调优总结 -Xms -Xmx -Xmn -Xss_jakeswang的博客-CSDN博客_jvm xms xmx 参数详解

看书:深入理解Java虚拟机

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏哦

相关文章

微信公众号

最新文章

更多