# Tomcat JVM 调优实践
# 一、JVM 基础架构
# 1.1 Jvm 执行过程
- 使用 java 编程语言开发 java 源代码,然后通过编译器把 java 编译为 java 类文件也叫字节码;
- 通过类加载器 ClassLoader 将字节码加载到内存中,将其放在运行时数据区中的方法区内;
- 然后调用执行引擎在 JVM 虚拟机中运行,然后将字节码编译成底层系统指令,在交由 CPU 执行;
- 同时我们在编写程序时,不可能从头到尾去实现所有代码功能,可以通过 Java API 调用本地库接口(Native Interface)当中已经有的 java 代码来实现其他的功能(比如图形库、语言等);
- 总结:类的加载指的是将类的.class 文件中的二进制数据读取到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个对象,用来封装类在方法区的数据结构;
# 1.2 JVM 堆内存模型
当 jvm 启动时,jvm 会自动从主机操作系统中取到一部分内存空间;
然后 jvm 会自动将内存空间按照特定的格式,进行区域划分;
划分区段的目的,主要是让垃圾回收算法能够更好的利用这些区域完成垃圾回收;
jvm Heap 内存空间有三部分组成:
- 年轻代
- 老年代
年轻代又可以划分三个子组成部分:
- 新生区(Eden):当一个对象刚刚创建时则创建在 Eden 中;
- 存活区(Suvivor):步入成熟区的初创对象;
- Suvivor to
- Suvivor from
老年代
那些活跃的对象,很久都没有被回收掉,那么就送到老年代;
如果年轻代内存不够,创建的对象过于庞大,则直接进入老年代,称之为分配担保;
# 1.3 JVM 堆内存参数分配
分配 JVM 内存空间,我们应该安装业务运行过程中,运行模式、或内存使用方式,来指定使用这些内存空间的大小,指定这些参数可以通过 java -opts 传递;
- -Xms:新生代和老年代初始空间;
- -Xmx:新生代和老年代总共可用的空间;
- -XX:NewSize:新生代初始空间;
- -XX:MaxNewSize:新生代最大空间
# 1.3.1 设置 java 项目的堆空间
[root@web01 java]# cat jvm_heap_params.java | |
import java.util.UUID; | |
/* | |
* ************************************************* | |
* | |
* java -Xms100m -Xmx100m -XX:+UseConcMarkSweepGC -cp . jvm_heap_params | |
* | |
* ************************************************* | |
* */ | |
public class jvm_heap_params { | |
public static void main(String[] args) { | |
try { | |
Thread.sleep(Integer.MAX_VALUE); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
# 1.3.2 编译代码
[root@web01 java]# javac jvm_heap_params.java | |
[root@web01 java]# ls | |
jvm_heap_params.class jvm_heap_params.java |
# 1.3.3 指定堆内存启动
[root@web01 java]# java -Xms200M -Xmx200M -cp . jvm_heap_params & | |
[root@web01 java]# jps | |
1664 jvm_heap_params | |
1676 Jps | |
#通过 jinfo -flags 查看当前项目的内存分配 | |
[root@web01 java]# jinfo -flags 1664 | |
Attaching to process ID 1664, please wait... | |
Debugger attached successfully. | |
Server compiler detected. | |
JVM version is 25.451-b10 | |
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=209715200 -XX:MaxHeapSize=209715200 -XX:MaxNewSize=69730304 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=69730304 -XX:OldSize=139984896 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC | |
Command line: -Xms200M -Xmx200M |
# 1.3.4 不指定堆内存启动
[root@web01 java]# java -cp . jvm_heap_params & | |
[root@web01 java]# jps | |
1763 Jps | |
1751 jvm_heap_params | |
#通过 jinfo -flags 查看当前项目的内存分配 | |
[root@web01 java]# jinfo -flags 1751 | |
Attaching to process ID 1751, please wait... | |
Debugger attached successfully. | |
Server compiler detected. | |
JVM version is 25.451-b10 | |
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=31457280 -XX:MaxHeapSize=478150656 -XX:MaxNewSize=159383552 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=10485760 -XX:OldSize=20971520 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC | |
Command line: |
# 1.4 Tomcat 设置堆空间
[root@web01 java]# vim /soft/tomcat/bin/catalina.sh | |
#添加如下内容 | |
... | |
JAVA_OPTS="$JAVA_OPTS $JSSE_OPTS" #下面添加 | |
JAVA_OPTS="$JAVA_OPTS -Xms200m -Xmx200m -XX:+UseConcMarkSweepGC" | |
#生产服务器配置参数 | |
JAVA_OPS="$JAVA_OPTS -Xms2G -Xmx2G -XX:+UseConcMarkSweepGC" | |
[root@web01 java]# systemctl stop tomcat && systemctl start tomcat |
# 二、 GC 垃圾回收算法
# 2.1 什么是垃圾
- GC 指的就是垃圾,垃圾指的是不在需要使用的 java 对象;
- 程序在运行的过程中需要申请内存资源的,而在运行过程中,可能会产生无效的对象资源,如果不及时处理则会占用内存,最后造成内存溢出;
- Java 有自动垃圾回收机制,也就是 GC,使得开发能更专注业务代码,而无需关心内存释放问题;
- 那我们需要知道哪些对象是垃圾;
- 以及这些垃圾对象该如何回收
# 2.2 如何确定哪些是垃圾
如何确定哪些是垃圾,其实就是判断 “对象的存活状态”,常见的判断分析有两种:
- 引用计数算法
- 可达性分析算法
# 2.2.1 引用计数算法
实现原理:
给每个 Java 对象添加一个引用计数器;
每当有一个地方引用它,计数器 + 1,引用失效则 - 1;
当计数器不为 0 时,则判断对象为存活,否则判断为死亡;
算法优势
实现简单
判断高效
算法缺陷
算法小结:
- 由于算法存在判断逻辑漏洞,所以 JVM 虚拟机没有采用该算法判断 JAVA 对象是否存活;
# 2.2.2 可达性分析算法
Java 虚拟机采用的算法,可达性分析算法;
- 将一系列 GC Roots 对象作为起点,从这些起点开始向下搜索;
- 当一个对象 GC Roots 没有任何引用链相连时,则判断对象不可达,可以被 GC 回收;
# 2.3 GC 垃圾回收算法
GC 通过 “可达性分析算法” 标记了垃圾对象以及存活对象,那么该如何将标记的垃圾对象回收呢?
- 标记 - 清除算法
- 标记 - 复制算法
- 标记 - 压缩算法
# 2.3.1 标记 - 清除算法
标记清除算法分为两个阶段:
- 标记阶段:标记出存活的对象,以及可回收的对象;
- 清除阶段:回收掉所有被标记的对象;
标记清除算法缺点:
- 效率不高,因为需要遍历所有的对象,然后标记存活对象,同时还要标记可回收对象;
- 清除对象后位置不连续,难以找到连续的可用空间,所以容易产生碎片;
# 2.3.2 标记 - 复制算法
算法基本概念:
- 首先将内存划分为两块相等大小的区域,每次数据存储仅使用其中一块区域;
- 当这一块区域使用完后,就将还存活的对象复制到另一块区域上面;
- 最后将使用过的内存空间进行一次清理;
算法工作过程:
标记复制算法优点:
- 在垃圾对象多的情况下,效率较高;
- 垃圾清理后,内存无碎片;
标记复制算法缺点:
- 分配的两块内存区域,在同一时刻,只能使用一半,造成空间让费;
- 如果创建的对象存活率较高时,需要在两个内存空间来回复制,效率低;
# 2.3.3 标记 - 压缩算法
算法思想:它与 “标记复制” 算法类似,但是标记压缩算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存,因此不会产生内存碎片;
算法工作过程:
# 三、GC 分代垃圾回收算法
由于 JVM 堆空间划分了年轻代和老年代,而且年轻代和老年代中存储对象各有特点,所以不同阶段适合使用不同的 GC 算法进行 GC 回收。
根据回收对象的特点进行选择,在 JVM 中:
- 年轻代对象存活时间低,适合使用 “复制算法”;
- 老年代对象存活时间长,适合使用 “标记清除算法、标记压缩算法”;
MinorGC/YoungGC:指年轻代触发 GC 动作
MajorGC/OldGC:指老年代触发 GC 动作
FullGC :年轻代和老年代都没有空间,则全部回收,称之为 FullGC
# 3.1 GC 年轻代垃圾回收 - MinorGC/YoungGC
# 3.1.1 MinorGC/YoungGC 回收阶段 1
- 刚刚启动 JVM 时,创建的对象会进入 Eden 区;
- 当 Eden 区满了后,如果任然有新的对象需要创建,则会触发一次 MinorGC/YoungGC
- 通过根可达算法标记垃圾对象;
- 将活跃的对象通过复制算法,复制到 S0 区域;
- 然后将 Eden 对象去全部清除;
- 清理完成后,Eden 中没有任何对象,S0 中有存活对象,S1 中没有任何对象;
# 3.1.2 MinorGC/YoungGC 回收阶段 2
- 当再次创建对象时,对象任然会创建在 Eden 区;
- 当 Eden 区空间满了后,会再次触发 GC 垃圾回收;
- 此时会将 Eden 区的对象复制到 S1 区;
- 将 S0 存活的对象复制到 S1 区;
- 然后清理所有 Eden 区对象及 S0 区对象;
# 3.2 GC 老年代垃圾回收 - MajorGC/OldGC
# 3.2.1 MajorGC/OldGC 回收阶段 1
- 当有较大对象创建时,可能不会出现在 Eden 区,而是直接进入老年代;
- 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 + 1,当年龄达到 15(Default)时,升级为老年代;
# 3.2.2 MajorGC/OldGC 回收阶段 2
- 当创建新对象,如果 Eden + Survivor +Old 都无法存储时,就会发生 MajorGC/OldGC;
- 遍历整个堆,用来标记不存在使用的对象;
- 将不在使用,或不存在访问的对象清除掉;
- 通常出现 MajorGC/OldGC 是指老年代发生了 GC;
- 当出现 MajorGC/OldGC 通常会伴随至少一次 MinorGC/YoungGC;
- 而 MajorGC/OldGC 的速度通常会比 MinorGC/YoungGC 慢 10 倍以上;
- 如果 MinorGC/YoungGC 回收资源后,还是没有空间,则会触发 FullGC;将老年代和年轻代内存全部清空,一旦触发 FullGC 就需要熬过漫长的时间,甚至有可能要手动进行垃圾回收;
# 3.3 Stop-The-World
- STW 是 Java 中一种全局暂停现象,多半有 GC 引起,所谓全局停顿,就是所有 Java 代码停止运行;
- 一旦发生 GC,就是触发短暂的 STW,就好像垃圾车从家门口驶过,什么也做不了;
- STW 容易造成服务器长时间停止,以及资源不响应,对于 HA 系统,可能还会应急主备切换;
# 四、GC 垃圾收集器
如果说 GC 垃圾回收算法只是内存回收的方法论,那 GC 垃圾收集器就是来具体实现这些算法,并实现内存回收;
# 4.1 GC 垃圾收集器类型
- 串行垃圾收集器:GC 单线程实现内存垃圾回收,会暂停所有用户线程,如 Serial;
- 并行垃圾收集器:GC 多线程并发实现内存垃圾回收,会暂停所有用户线程,如 ParNew、Parallel;
- 并发垃圾收集器:用户线程和 GC 线程同时执行实现内存垃圾回收(不一定完全并行,可能交替执行),不停顿用户进程,或短暂停顿,如 CMS;
Serial(复制算法)新生代单线程收集器,标记和清理都是单线程,优点简单高效;
Serial Old(标记整理算法)老年代单线程收集器,Serial 收集器的老年代版本;
ParNew(复制算法)新生代并行收集器,是 Serial 收集器的多线程版本,在多核 CPU 环境下会比 Serial 表现更好;
Parallel Scavenge(复制算法)新生代并行收集器,追求高吞吐量,高效利用 CPU;
Parallel Old(标记整理算法)Parallel Scavenge 老年代并行收集器,吞吐量优先;
CMS(Concurrent Mark Sweep)(标记清除算法)老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短 GC 回收停顿时间;
G1(Garbage First)(标记整理算法)并行收集器,G1 回收的范围是整个 Java 堆,包括新生代、老年代,而前六种收集器回收的范围仅限于新生代或老年代。
# 4.2 修改默认 GC 收集器
Java 默认垃圾收集器为年轻代使用 Parallel Scavenge 垃圾收集器,老年代使用 Parallel Old 垃圾收集器,Tomcat 可以通过如下参数进行修改:
[root@web01 java]# vim /soft/tomcat/bin/catalina.sh | |
#添加如下内容 | |
... | |
JAVA_OPTS="$JAVA_OPTS $JSSE_OPTS" #下面添加 | |
JAVA_OPS="$JAVA_OPTS -Xms2G -Xmx2G -XX:+UseConcMarkSweepGC" | |
[root@web01 java]# systemctl stop tomcat && systemctl start tomcat |
# 4.3 串行垃圾收集器 Serial
# 4.3.1 Serial 基本概念
- 串行垃圾收集器是最基本的、发展历史最悠久的收集器;
- 特点:单线程、简单高效、对于限定单个 CPU 的环境来说,Serial 收集器由于没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率;
- 注意:串行垃圾收集器在进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束;
# 4.3.2 Serial 运行示意图
# 4.3.3 SerialGC 代码测试案例
#1. 编写 java 代码 GC | |
[root@web01 java]# cat SerialGC.java | |
import java.util.UUID; | |
/* | |
* ************************************************* | |
* | |
* 思路:while循环中不断拼接字符串,直到oom异常 | |
* java -Xms10m -Xmx10m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags \ | |
* -cp . SerialGC | |
* | |
* ************************************************* | |
* */ | |
public class SerialGC { | |
public static void main(String[] args) { | |
String str = "Oldxu"; | |
while (true) { | |
str += str + UUID.randomUUID(); | |
str.intern(); | |
} | |
} | |
} | |
[root@web01 java]# javac SerialGC.java | |
#2. 执行 java 代码,指定对应的 GC 收集器 | |
# -XX:+UseSerialGC 表示使用的 SerialGC | |
# 年轻代采用 SerialGC, 复制算法 | |
# 老年代采用 SerialGCOld, 标记整理算法 | |
# GC 日志 https://www.cnblogs.com/gouge/p/9112782.html | |
[root@web01 java]# java -Xms10m -Xmx10m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -cp . SerialGC | |
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC | |
[GC (Allocation Failure) [DefNew: 2747K->320K(3072K), 0.0011904 secs] 2747K->1148K(9920K), 0.0012255 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [DefNew: 2341K->0K(3072K), 0.0009890 secs] 3169K->2132K(9920K), 0.0010105 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [DefNew: 2678K->0K(3072K), 0.0006912 secs] 4810K->3444K(9920K), 0.0007214 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] | |
[GC (Allocation Failure) [DefNew: 1366K->0K(3072K), 0.0004334 secs] 4810K->4756K(9920K), 0.0004566 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [DefNew (promotion failed) : 2678K->2678K(3072K), 0.0002689 secs][Tenured: 4755K->3771K(6848K), 0.0028598 secs] 7434K->3771K(9920K), [Metaspace: 3013K->3013K(1056768K)], 0.0031812 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [DefNew: 2678K->0K(3072K), 0.0018376 secs] 6449K->6395K(9920K), 0.0018952 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [DefNew: 2677K->2677K(3072K), 0.0000409 secs][Tenured: 6395K->4427K(6848K), 0.0032290 secs] 9073K->4427K(9920K), [Metaspace: 3013K->3013K(1056768K)], 0.0033621 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[Full GC (Allocation Failure) [Tenured: 4427K->4408K(6848K), 0.0028646 secs] 4427K->4408K(9920K), [Metaspace: 3013K->3013K(1056768K)], 0.0028871 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space | |
at java.util.Arrays.copyOf(Arrays.java:3332) | |
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) | |
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) | |
at java.lang.StringBuilder.append(StringBuilder.java:142) | |
at java.lang.StringBuilder.append(StringBuilder.java:137) | |
at SerialGC.main(SerialGC.java:17) | |
Heap | |
def new generation total 3072K, used 109K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000) | |
eden space 2752K, 3% used [0x00000000ff600000, 0x00000000ff61b5a8, 0x00000000ff8b0000) | |
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000) | |
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000) | |
tenured generation total 6848K, used 4408K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000) | |
the space 6848K, 64% used [0x00000000ff950000, 0x00000000ffd9e2b0, 0x00000000ffd9e400, 0x0000000100000000) | |
Metaspace used 3046K, capacity 4486K, committed 4864K, reserved 1056768K | |
class space used 330K, capacity 386K, committed 512K, reserved 1048576K |
# 4.4 并行垃圾收集器 ParNew
# 4.4.1 ParNew 基本概念
并行垃圾收集器在串行垃圾收集器的基础上做了改进,将单线程改为了多线程进行垃圾回收,这样可以缩短垃圾回收的时间,但还是会存在 STW ( Stop-The-World)的问题。
# 4.4.2 ParNew 运行示意图
# 4.4.3 ParNewGC 代码测试案例
#1. 编写 java 代码 GC | |
[root@web01 java]# cat ParNewGC.java | |
import java.util.UUID; | |
/* | |
* ************************************************* | |
* | |
* 思路:while循环中不断拼接字符串,直到oom异常 | |
* java -Xms10m -Xmx10m -XX:+UseParNewGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags \ | |
* -cp . ParNewGC | |
* | |
* ************************************************* | |
* */ | |
public class ParNewGC { | |
public static void main(String[] args) { | |
String str = "Oldxu"; | |
while (true) { | |
str += str + UUID.randomUUID(); | |
str.intern(); | |
} | |
} | |
} | |
[root@web01 java]# javac ParNewGC.java | |
# 2. 执行 java 代码,指定对应的 GC 收集器 | |
# 使用年轻代 UseParNew GC 默认激活老年代 SeariaOld GC(单线程) | |
[root@web01 java]# java -Xms10m -Xmx10m -XX:+UseParNewGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -cp . ParNewGC | |
Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release | |
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParNewGC | |
[GC (Allocation Failure) [ParNew: 2747K->320K(3072K), 0.0010884 secs] 2747K->1160K(9920K), 0.0011451 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [ParNew: 2341K->227K(3072K), 0.0019999 secs] 3181K->2380K(9920K), 0.0020246 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [ParNew: 2905K->28K(3072K), 0.0011000 secs] 5058K->3493K(9920K), 0.0011400 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [ParNew: 1394K->7K(3072K), 0.0011553 secs] 4859K->4783K(9920K), 0.0011894 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [ParNew (promotion failed): 2685K->2679K(3072K), 0.0006935 secs][Tenured: 4776K->3771K(6848K), 0.0032073 secs] 7461K->3771K(9920K), [Metaspace: 3010K->3010K(1056768K)], 0.0039609 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] | |
[GC (Allocation Failure) [ParNew: 2678K->2K(3072K), 0.0011295 secs] 6449K->6397K(9920K), 0.0011634 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [ParNew: 2679K->2679K(3072K), 0.0000115 secs][Tenured: 6395K->4427K(6848K), 0.0039025 secs] 9075K->4427K(9920K), [Metaspace: 3010K->3010K(1056768K)], 0.0039539 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] | |
[Full GC (Allocation Failure) [Tenured: 4427K->4408K(6848K), 0.0024659 secs] 4427K->4408K(9920K), [Metaspace: 3010K->3010K(1056768K)], 0.0024877 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space | |
at java.util.Arrays.copyOf(Arrays.java:3332) | |
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) | |
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) | |
at java.lang.StringBuilder.append(StringBuilder.java:142) | |
at java.lang.StringBuilder.append(StringBuilder.java:137) | |
at ParNewGC.main(ParNewGC.java:17) | |
Heap | |
par new generation total 3072K, used 109K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000) | |
eden space 2752K, 3% used [0x00000000ff600000, 0x00000000ff61b5a8, 0x00000000ff8b0000) | |
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000) | |
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000) | |
tenured generation total 6848K, used 4408K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000) | |
the space 6848K, 64% used [0x00000000ff950000, 0x00000000ffd9e208, 0x00000000ffd9e400, 0x0000000100000000) | |
Metaspace used 3044K, capacity 4486K, committed 4864K, reserved 1056768K | |
class space used 330K, capacity 386K, committed 512K, reserved 1048576K |
# 4.5 并行垃圾收集器 Parallel
# 4.5.1 Parallel 基本概念
并行垃圾收集器在串行垃圾收集器的基础上做了改进,将单线程改为了多线程进行垃圾回收,这样可以缩短垃圾回收的时间,但还是会存在 STW ( Stop-The-World)的问题。
# 4.5.2 Parallel 运行示意图
# 4.5.3 ParallelGC 代码测试案例
#1. 编写 java 代码 GC | |
[root@web01 java]# cat ParallelGC.java | |
import java.util.UUID; | |
/* | |
* ************************************************* | |
* | |
* 思路:while循环中不断拼接字符串,直到oom异常 | |
* java -Xms10m -Xmx10m -XX:+UseParallelGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags \ | |
* -cp . ParallelGC | |
* | |
* ************************************************* | |
* */ | |
public class ParallelGC { | |
public static void main(String[] args) { | |
String str = "Oldxu"; | |
while (true) { | |
str += str + UUID.randomUUID(); | |
str.intern(); | |
} | |
} | |
} | |
[root@web01 java]# javac ParallelGC.java | |
# 2. 执行 java 代码,指定对应的 GC 收集器 | |
# 使用年轻代 ParallelScauenge GC 默认激活老年代 ParallelOld GC(单线程) | |
[root@web01 java]# java -Xms10m -Xmx10m -XX:+UseParallelGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -cp . ParallelGC | |
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC | |
[GC (Allocation Failure) [PSYoungGen: 1744K->501K(2560K)] 1744K->773K(9728K), 0.0013675 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [PSYoungGen: 2509K->503K(2560K)] 2781K->843K(9728K), 0.0006946 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [PSYoungGen: 2184K->240K(2560K)] 3835K->2547K(9728K), 0.0022336 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] | |
[GC (Allocation Failure) [PSYoungGen: 2248K->240K(2560K)] 8491K->6483K(9728K), 0.0007844 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) --[PSYoungGen: 1592K->1592K(2560K)] 7836K->7836K(9728K), 0.0011287 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] | |
[Full GC (Ergonomics) [PSYoungGen: 1592K->0K(2560K)] [ParOldGen: 6243K->3114K(7168K)] 7836K->3114K(9728K), [Metaspace: 3009K->3009K(1056768K)], 0.0064498 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] | |
[GC (Allocation Failure) [PSYoungGen: 40K->64K(2560K)] 5779K->5802K(9728K), 0.0007213 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [PSYoungGen: 64K->32K(1536K)] 5802K->5770K(8704K), 0.0010992 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(1536K)] [ParOldGen: 5738K->4427K(7168K)] 5770K->4427K(8704K), [Metaspace: 3009K->3009K(1056768K)], 0.0030299 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] | |
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4427K->4427K(9216K), 0.0007654 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4427K->4408K(7168K)] 4427K->4408K(9216K), [Metaspace: 3009K->3009K(1056768K)], 0.0056029 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] | |
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space | |
at java.util.Arrays.copyOf(Arrays.java:3332) | |
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) | |
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) | |
at java.lang.StringBuilder.append(StringBuilder.java:142) | |
at java.lang.StringBuilder.append(StringBuilder.java:137) | |
at ParallelGC.main(ParallelGC.java:17) | |
Heap | |
PSYoungGen total 2048K, used 41K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000) | |
eden space 1024K, 4% used [0x00000000ffd00000,0x00000000ffd0a428,0x00000000ffe00000) | |
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) | |
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) | |
ParOldGen total 7168K, used 4408K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000) | |
object space 7168K, 61% used [0x00000000ff600000,0x00000000ffa4e218,0x00000000ffd00000) | |
Metaspace used 3042K, capacity 4486K, committed 4864K, reserved 1056768K | |
class space used 330K, capacity 386K, committed 512K, reserved 1048576K |
# 4.6 并发垃圾收集器 CMS
# 4.6.1 CMS 基本概念
- CMS 全程 Concurrent Mark Sweep,是一款并发的、使用标记清除算法的垃圾回收器;
- CMS 回收器是针对老年代进行垃圾回收,它在某些阶段尽量与工作线程一起运行,以获取最短响应时间,减少 STW 时长(200ms 以内),提升响应速度,是互联网系统上较佳的回收算法;
- 通过 - XX:+UseConcMarkSweepGC 参数启用老年代 CMS 回收器,会同时激活年轻代的 ParNEW 回收器;
# 4.6.2 CMS 运行示意图
- CMS 优点:低停顿,并发执行;
- CMS 缺点:
- 并发执行对 CPU 资源压力大;
- 无法处理在处理过程中产生的垃圾;
- 采用标记清除算法会导致大量碎片,从而在分配大对象时,可能会触发 FullGC;
# 4.6.3 CMS GC 代码测试案例
# 1. 编写 java 代码 GC | |
[root@web01 java]# cat CMSGC.java | |
import java.util.UUID; | |
/* | |
* ************************************************* | |
* | |
* 思路:while循环中不断拼接字符串,直到oom异常 | |
* java -Xms10m -Xmx10m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags \ | |
* -cp . CMSGC | |
* | |
* ************************************************* | |
* */ | |
public class CMSGC { | |
public static void main(String[] args) { | |
String str = "Oldxu"; | |
while (true) { | |
str += str + UUID.randomUUID(); | |
str.intern(); | |
} | |
} | |
} | |
[root@web01 java]# javac CMSGC.java | |
# 2. 执行 java 代码,指定对应的 GC 收集器 | |
# 使用年轻代 ParNewGC 默认激活老年代 ConcMarkSweepGC | |
[root@web01 java]# java -Xms10m -Xmx10m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -cp . CMSGC | |
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=3497984 -XX:MaxTenuringThreshold=6 -XX:NewSize=3497984 -XX:OldPLABSize=16 -XX:OldSize=6987776 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC | |
[GC (Allocation Failure) [ParNew: 2747K->320K(3072K), 0.0023735 secs] 2747K->1159K(9920K), 0.0024383 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [ParNew: 2341K->116K(3072K), 0.0018810 secs] 3181K->2261K(9920K), 0.0019107 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [ParNew: 2794K->58K(3072K), 0.0009690 secs] 4938K->3514K(9920K), 0.0010009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [ParNew: 1424K->14K(3072K), 0.0007894 secs] 4880K->4783K(9920K), 0.0008156 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (CMS Initial Mark) [1 CMS-initial-mark: 4768K(6848K)] 7460K(9920K), 0.0004243 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[CMS-concurrent-mark-start] | |
[GC (Allocation Failure) [ParNew (promotion failed): 2692K->2681K(3072K), 0.0004579 secs][CMS[CMS-concurrent-mark: 0.001/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
(concurrent mode failure): 4768K->3778K(6848K), 0.0040684 secs] 7460K->3778K(9920K), [Metaspace: 3008K->3008K(1056768K)], 0.0046737 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] | |
[GC (Allocation Failure) [ParNew: 2678K->2K(3072K), 0.0007443 secs] 6456K->6408K(9920K), 0.0008044 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (CMS Initial Mark) [1 CMS-initial-mark: 6406K(6848K)] 9031K(9920K), 0.0003723 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[CMS-concurrent-mark-start] | |
[CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[CMS-concurrent-preclean-start] | |
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[CMS-concurrent-abortable-preclean-start] | |
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (CMS Final Remark) [YG occupancy: 2679 K (3072 K)][Rescan (parallel) , 0.0010492 secs][weak refs processing, 0.0000088 secs][class unloading, 0.0002491 secs][scrub symbol table, 0.0003293 secs][scrub string table, 0.0002142 secs][1 CMS-remark: 6406K(6848K)] 9085K(9920K), 0.0019372 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] | |
[CMS-concurrent-sweep-start] | |
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[CMS-concurrent-reset-start] | |
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (Allocation Failure) [ParNew: 2679K->2K(3072K), 0.0008976 secs][CMS: 5749K->4433K(6848K), 0.0029519 secs] 5805K->4433K(9920K), [Metaspace: 3009K->3009K(1056768K)], 0.0038988 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] | |
[Full GC (Allocation Failure) [CMS: 4433K->4414K(6848K), 0.0027747 secs] 4433K->4414K(9920K), [Metaspace: 3009K->3009K(1056768K)], 0.0028025 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (CMS Initial Mark) [1 CMS-initial-mark: 4414K(6848K)] 4468K(9920K), 0.0004950 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[CMS-concurrent-mark-start] | |
Exception in thread "main" [CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[CMS-concurrent-preclean-start] | |
java.lang.OutOfMemoryError: Java heap space | |
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[GC (CMS Final Remark) [YG occupancy: 54 K (3072 K)][Rescan (parallel) , 0.0006640 secs][weak refs processing, 0.0000071 secs][class unloading, 0.0002738 secs][scrub symbol table, 0.0003376 secs][scrub string table, 0.0002713 secs][1 CMS-remark: 4414K(6848K)] 4468K(9920K), 0.0016334 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] | |
[CMS-concurrent-sweep-start] | |
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
[CMS-concurrent-reset-start] | |
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] | |
at java.util.Arrays.copyOf(Arrays.java:3332) | |
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) | |
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) | |
at java.lang.StringBuilder.append(StringBuilder.java:142) | |
at java.lang.StringBuilder.append(StringBuilder.java:137) | |
at CMSGC.main(CMSGC.java:17) | |
Heap | |
par new generation total 3072K, used 109K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000) | |
eden space 2752K, 3% used [0x00000000ff600000, 0x00000000ff61b5a8, 0x00000000ff8b0000) | |
from space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000) | |
to space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000) | |
concurrent mark-sweep generation total 6848K, used 477K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000) | |
Metaspace used 3042K, capacity 4486K, committed 4864K, reserved 1056768K | |
class space used 330K, capacity 386K, committed 512K, reserved 1048576K |
# 五、JVM 性能调优实践
- 调优一般包含多个层次,如:架构调优、代码调优、数据库调优、JVM 调优、操作系统调优;
- 架构调优和代码调优是 JVM 调优的基础;
- 当我们的架构和代码都已经调优后,还不能满足业务需求,才需要进行 JVM 调优;
# 5.1 什么是 JVM 调优
既然要进行 JVM 调优,那么就一定需要量化指标;
JVM 调优原则:尽可能使用较小的内存和 CPU 来让 JAVA 程序获得更高的吞吐量以及较低的延迟;
- 吞吐量:是指不考虑垃圾收集器引起的停顿时间或内存消耗,应用达到的最高性能指标
- 低延迟:GC 停顿时间端,以及 GC 触发频率低;
- 低内存:比较低的内存空间,能够获取程序的稳定运行;
值得注意的是:任何一个指标性能的提高,几乎都是以牺牲其他性能指标的损耗为代价,两者不可兼得,具体需要根据业务的重要性做权衡;
# 5.2 何时不需要 JVM 调优
- MinorGC 执行时间不到 50ms;
- MinorGC 执行不频繁,约 10s 一次;
- FullGC 执行时间不到 1s;
# 5.3 何时需要 JVM 调优
当运行业务代码出现如下情况,则需要考虑进行 JVM 调优,但前提是代码没有办法进行优化了:
- FullGC 次数频繁:FullGC 如果很频繁的话,基本上可以肯定是分配的内存不够用;
- GC 停顿时间过长 1s:GC 过长会导致 STM 时间过长,肯定是内存堆空间分配不合理;
- 应用出现 OOM 内存溢出;
- 系统吞吐量与响应性能不高或持续下降:一般是 GC 停顿问题造成;
# 5.4 JVM 调优常规方法
JVM 调优是一个手段,但是并不是定所有问题都可以通过 JVM 进行调优解决;
- 大多数的 JAVA 应用不需要进行 JVM 调优;
- 首先项目还没有复杂到需要做 JVM 调优的程序,而且现在的机器内存都很大,可以尽情使用;
- 其次 JVM 调优有一定难度,需要有相应工程调优经验,否则没有作用,或者造成性能反而下降;
- 大多数导致 GC 问题的原因是代码层面的问题导致;
- 选择合适的 GC 收集器,并设置合理的堆空间参数;
- 减少使用全局变量和大对象,减少创建对象的数量;
- 全局变量几乎不会被回收;
- 大对象会直接进入老年代,从而造成 MajorGC;
- 分析 GC 日志优化代码比优化 JVM 参数效果更好;
- 优先架构调优和代码调优,JVM 优化优先级放到最低;
- JVM 堆设置,一般通过 - Xms、-Xmx 限定最小值、最大值,为了避免垃圾收集器在最小、最大之间收缩堆而产生额外的时间和资源开销,通常把最大、最小设置为相同的值;
# 5.5 JVM 各区域的默认值
参考链接:https://blog.csdn.net/qq_27184497/article/details/119055669
- 堆空间初始值:由 - Xms 指定,默认是物理内存的 1/64。比如电脑内存是 16G,由此得出公式为:16*1024/64=256M
- 堆空间最大值:由 - Xmx 指定,默认是物理内存的 1/4。比如我电脑内存是 16G,由此得出公式为:16/4=4G
- 老年代 :占用整个堆内存 2/3 的空间,比如我目前堆内存是 4G,由此得出公式为:4*1024*(2/3)≈ 2730
- 新生代:占用整个堆的 1/3 的空间;实际可用的是 4*1024*(1/3)≈ 1365 ,另外新生代实际可用的内存为 1365*80% ;
新生代通过以下参数设置:
-XX:NewSize=256m #设置新生代初始内存 | |
-XX:MaxNewSize=256m #设置新生代最大内存 | |
-Xmn256m #将 NewSize 与 MaxNewSize 设为一致 256m |
可以自行指定的参数:
-Xms500M #指定初始堆空间大小; | |
-Xmx500M #最大的堆空间大小; | |
-XX:NewRatio=1 #调整新生代和老年的比率; 1:1, 默认是 1:2 | |
-XX:SurvivorRatio=6 #调整年轻代中 Eden 和 Sur 比率: 6:2:2 默认 8:1:1 | |
#参数示例 | |
-Xms500M -Xmx500M -XX:SurvivorRatio=6 -XX:NewRatio=1 | |
新生代: 250MB | |
Eden: 150M 60% | |
From: 50M 20% | |
To: 50M 20% | |
老年代: 250MB |
# 5.6 JVM 优化代码示例一
#1. 示例代码 | |
[root@web01 java]# cat MoreMinorGC.java | |
@SuppressWarnings("all") | |
public class MoreMinorGC { | |
private static void minorGC() throws InterruptedException { | |
byte[] x = new byte[1024 * 1024]; // 在 Eden 区域放入一个 1MB 的对象 | |
x = new byte[1024 * 1024]; | |
x = new byte[1024 * 1024]; // 会导致前两个 1MB 的对象成为垃圾对象 | |
x = null; // 将之前的三个 1MB 的对象都变成垃圾对象 | |
// 这句代码就会触发年轻代的 Minor GC | |
byte[] y = new byte[2 * 1024 * 1024]; // 在 Eden 区中分配一个 2MB 的对象 | |
Thread.sleep(1000); | |
} | |
public static void main(String[] args) throws InterruptedException { | |
while (true) { | |
minorGC(); | |
} | |
} | |
} | |
#2. 编译代码 | |
[root@web01 java]# javac MoreMinorGC.java | |
#3. 运行代码 | |
[root@web01 java]# java \ | |
-XX:NewSize=5M \ | |
-XX:MaxNewSize=5M \ | |
-XX:InitialHeapSize=10M \ | |
-XX:MaxHeapSize=10M \ | |
-XX:SurvivorRatio=8 \ | |
-XX:+UseParNewGC \ | |
-XX:+UseConcMarkSweepGC \ | |
-XX:+PrintGCDetails \ | |
-XX:+PrintCommandLineFlags \ | |
-cp . MoreMinorGC | |
#4. 代码运行参数描述 | |
-XX:NewSize=5M #新生代初始内存 | |
-XX:MaxNewSize=5M #新生代最大内存 | |
-XX:InitialHeapSize=10M #初始堆空间 Xms | |
-XX:MaxHeapSize=10M #最大堆空间 Xmx | |
-XX:SurvivorRatio=8 #Eden、Survivore from、Survivore to 比例为 8:1:1 | |
-XX:+UseParNewGC #新生代使用 ParNewGC | |
-XX:+UseConcMarkSweepGC #老年代使用 CMSGC | |
-XX:+PrintGCDetails #打印 GC 日志 | |
-XX:+PrintCommandLineFlags #打印 GC 日志 | |
#5. 追踪 GC | |
[root@web01 ~]# jps | |
8325 MoreMinorGC | |
6553 Bootstrap | |
8361 Jps | |
6574 plugin-core.jar | |
[root@web01 ~]# jstat -gc 8325 | |
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT | |
512.0 512.0 0.0 0.0 4096.0 2048.0 5120.0 3320.4 4864.0 2479.2 512.0 264.9 199 0.057 49 0.028 0.08 | |
#6 将程序运行的堆内存扩大 10 倍 | |
[root@web01 java]# java \ | |
-XX:NewSize=50M \ | |
-XX:MaxNewSize=50M \ | |
-XX:InitialHeapSize=100M \ | |
-XX:MaxHeapSize=100M \ | |
-XX:SurvivorRatio=8 \ | |
-XX:+UseParNewGC \ | |
-XX:+UseConcMarkSweepGC \ | |
-XX:+PrintGCDetails \ | |
-XX:+PrintCommandLineFlags \ | |
-cp . MoreMinorGC | |
#7. 追踪 GC,发现 YGC 没有那么频繁了,同时没有产生 FGC,GCT 时间明显变短了 | |
[root@web01 ~]# jps | |
8400 MoreMinorGC | |
6553 Bootstrap | |
6574 plugin-core.jar | |
8414 Jps | |
[root@web01 ~]# jstat -gc 8400 | |
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT | |
5120.0 5120.0 1414.5 0.0 40960.0 39715.8 51200.0 0.0 4864.0 2479.2 512.0 264.9 2 0.008 0 0.000 0.00 | |
#8.GC 参数描述 | |
S0C:Surivor区S0的大小 | |
S1C:Surivor区S1的大小 | |
S0U:Surivor区S0的使用大小 | |
S1U:Surivor区S1的使用大小 | |
EC: Eden区的大小 | |
EU:Eden区的使用大小 | |
OC:老年代大小 | |
OU:老年代使用大小 | |
MC:方法区大小 | |
MU:方法区使用大小 | |
CCSC:压缩类空间大小 | |
CCSU:压缩类空间使用大小 | |
YGC:年轻代垃圾回收次数 | |
YGCT:年轻代垃圾回收消耗时间 | |
FGC:老年代垃圾回收次数 | |
FGCT:老年代垃圾回收消耗时间 | |
GCT:垃圾回收消耗总时间 | |
单位:KB |
# 5.7 JVM 优化代码示例二 FullGC
FullGc:清理整个内存堆,即包括年轻的也包括老年代
#1. 示例代码 | |
[root@web01 java]# cat MoreFullGC.java | |
import java.time.LocalDate; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.concurrent.ScheduledThreadPoolExecutor; | |
import java.util.concurrent.ThreadPoolExecutor; | |
import java.util.concurrent.TimeUnit; | |
/* | |
优化前 | |
-Xms20M -Xmx20M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps | |
优化后 | |
-Xms200M -Xmx200M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps | |
*/ | |
@SuppressWarnings("all") | |
public class MoreFullGC { | |
private static class Oldxu { | |
private String name = "oldxu"; | |
private int age = 18; | |
private String gender = "male"; | |
private LocalDate birthday = LocalDate.MAX; | |
public void func() { | |
// | |
} | |
} | |
/** 线程池 */ | |
private static final ScheduledThreadPoolExecutor executor = | |
new ScheduledThreadPoolExecutor(50, | |
new ThreadPoolExecutor.DiscardOldestPolicy()); | |
private static void processOldxu(List<Oldxu> Oldxu) { | |
Oldxu.forEach(i -> executor.scheduleWithFixedDelay( | |
i::func, 2, 3, TimeUnit.SECONDS | |
)); | |
} | |
private static List<Oldxu> getAllOldxu(int count) { | |
List<Oldxu> Oldxu = new ArrayList<>(count); | |
for (int i = 0; i != count; ++i) { | |
Oldxu.add(new Oldxu()); | |
} | |
return Oldxu; | |
} | |
public static void main(String[] args) throws InterruptedException { | |
executor.setMaximumPoolSize(50); | |
while (true) { | |
processOldxu(getAllOldxu(100)); | |
Thread.sleep(100); | |
} | |
} | |
} | |
#2. 编译代码 | |
[root@web01 java]# javac MoreFullGC.java | |
#3. 运行代码 | |
[root@web01 java]# java -Xms20M -Xmx20M \ | |
-XX:+UseParNewGC \ | |
-XX:+UseConcMarkSweepGC \ | |
-XX:+PrintGCDetails \ | |
-XX:+PrintCommandLineFlags \ | |
-cp . MoreFullGC | |
#4. 追踪 GC | |
[root@web01 ~]# jps | |
6553 Bootstrap | |
8492 MoreFullGC | |
8557 Jps | |
6574 plugin-core.jar | |
[root@web01 ~]# jstat -gc 8492 | |
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT | |
640.0 640.0 0.0 0.0 5504.0 4440.6 13696.0 13696.0 4864.0 3916.4 512.0 433.9 10 0.036 53 0.182 0.21 | |
#5. 将程序运行的堆内存扩大 10 倍 | |
[root@web01 java]# java -Xms200M -Xmx200M \ | |
-XX:+UseParNewGC \ | |
-XX:+UseConcMarkSweepGC \ | |
-XX:+PrintGCDetails \ | |
-XX:+PrintCommandLineFlags \ | |
-cp . MoreFullGC | |
#6. 追踪 GC,发现 YGC 没有那么频繁了,同时没有产生 FGC,GCT 时间明显变短了 | |
[root@web01 ~]# jps | |
8696 Jps | |
6553 Bootstrap | |
8633 MoreFullGC | |
6574 plugin-core.jar | |
[root@web01 ~]# jstat -gc 8633 | |
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT | |
6784.0 6784.0 0.0 993.6 54656.0 38837.2 136576.0 0.0 4864.0 3906.9 512.0 436.1 1 0.009 0 0.000 0.00 | |
#7.GC 参数描述 | |
S0C:Surivor区S0的大小 | |
S1C:Surivor区S1的大小 | |
S0U:Surivor区S0的使用大小 | |
S1U:Surivor区S1的使用大小 | |
EC: Eden区的大小 | |
EU:Eden区的使用大小 | |
OC:老年代大小 | |
OU:老年代使用大小 | |
MC:方法区大小 | |
MU:方法区使用大小 | |
CCSC:压缩类空间大小 | |
CCSU:压缩类空间使用大小 | |
YGC:年轻代垃圾回收次数 | |
YGCT:年轻代垃圾回收消耗时间 | |
FGC:老年代垃圾回收次数 | |
FGCT:老年代垃圾回收消耗时间 | |
GCT:垃圾回收消耗总时间 | |
单位:KB |
# 六、 Tomcat 开启 JMX 监控详解
# 6.1 Windows 安装 JAVA
参考链接:https://blog.csdn.net/qq_39652397/article/details/124045623
# 6.2 Tomcat 启动 JMX 远程管理端口
[root@web01 ~]# vim /soft/tomcat/bin/catalina.sh
#!/bin/sh
CATALINA_OPTS="$CATALINA_OPTS \
-Dcom.sun.management.jmxremote \
-DJava.rmi.server.hostname=192.168.40.101 \
-Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false"
...
[root@web01 ~]# systemctl stop tomcat && systemctl start tomcat
[root@web01 ~]# netstat -lntp|grep java
tcp6 0 0 :::9999 :::* LISTEN 9725/java
tcp6 0 0 :::8080 :::* LISTEN 9725/java
# 6.3 通过 Jconsole 监控 jvm
Jconsole-> 远程进程 192.168.40.101:9999-> 连接
# 6.4 通过 Jvisualvm 监控 jvm
1、安装 visualvm
在高版本 JDK(大于 1.8 或后期更新的 1.8 版本)中已经不会再自动集成 VisualVM,需手动安装;
2、添加远程主机
visualvm->remote->ADD remote host->192.168.40.101->ADD JMX connection->192.168.40.101:9999
3、安装 GC 插件
Tools->plugins->Auvilable pluginS->Visual GC->install