# Tomcat JVM 调优实践

# 一、JVM 基础架构

# 1.1 Jvm 执行过程
  1. 使用 java 编程语言开发 java 源代码,然后通过编译器把 java 编译为 java 类文件也叫字节码;
  2. 通过类加载器 ClassLoader 将字节码加载到内存中,将其放在运行时数据区中的方法区内;
  3. 然后调用执行引擎在 JVM 虚拟机中运行,然后将字节码编译成底层系统指令,在交由 CPU 执行;
  4. 同时我们在编写程序时,不可能从头到尾去实现所有代码功能,可以通过 Java API 调用本地库接口(Native Interface)当中已经有的 java 代码来实现其他的功能(比如图形库、语言等);
  5. 总结:类的加载指的是将类的.class 文件中的二进制数据读取到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个对象,用来封装类在方法区的数据结构;

1.jpg

# 1.2 JVM 堆内存模型
  • 当 jvm 启动时,jvm 会自动从主机操作系统中取到一部分内存空间;

  • 然后 jvm 会自动将内存空间按照特定的格式,进行区域划分;

  • 划分区段的目的,主要是让垃圾回收算法能够更好的利用这些区域完成垃圾回收;

  • jvm Heap 内存空间有三部分组成:

    • 年轻代
    • 老年代
  • 年轻代又可以划分三个子组成部分:

    • 新生区(Eden):当一个对象刚刚创建时则创建在 Eden 中;
    • 存活区(Suvivor):步入成熟区的初创对象;
      • Suvivor to
      • Suvivor from
  • 老年代

    • 那些活跃的对象,很久都没有被回收掉,那么就送到老年代;

    • 如果年轻代内存不够,创建的对象过于庞大,则直接进入老年代,称之为分配担保;

3.jpg

# 1.3 JVM 堆内存参数分配

分配 JVM 内存空间,我们应该安装业务运行过程中,运行模式、或内存使用方式,来指定使用这些内存空间的大小,指定这些参数可以通过 java -opts 传递;

  • -Xms:新生代和老年代初始空间;
  • -Xmx:新生代和老年代总共可用的空间;
  • -XX:NewSize:新生代初始空间;
  • -XX:MaxNewSize:新生代最大空间

4.jpg

# 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

5.jpg

# 二、 GC 垃圾回收算法

# 2.1 什么是垃圾
  • GC 指的就是垃圾,垃圾指的是不在需要使用的 java 对象;
  • 程序在运行的过程中需要申请内存资源的,而在运行过程中,可能会产生无效的对象资源,如果不及时处理则会占用内存,最后造成内存溢出;
  • Java 有自动垃圾回收机制,也就是 GC,使得开发能更专注业务代码,而无需关心内存释放问题;
    • 那我们需要知道哪些对象是垃圾;
    • 以及这些垃圾对象该如何回收
# 2.2 如何确定哪些是垃圾

如何确定哪些是垃圾,其实就是判断 “对象的存活状态”,常见的判断分析有两种:

  • 引用计数算法
  • 可达性分析算法
# 2.2.1 引用计数算法
  • 实现原理:

    • 给每个 Java 对象添加一个引用计数器;

    • 每当有一个地方引用它,计数器 + 1,引用失效则 - 1;

    • 当计数器不为 0 时,则判断对象为存活,否则判断为死亡;

  • 算法优势

    • 实现简单

    • 判断高效

  • 算法缺陷

    • 每次对象被引用,都需要跟新计数器,存在时间开销;
    • 即使内存够用,在运行时进行计数,会让费 CPU 资源
    • 无法解决对象相互循环引用的问题,比如:
      • objA.name = objB;
      • objB.name = objA;
      • objA、objB 他们的计数器至少为 1,所以不可能被回收;
  • 算法小结:

    • 由于算法存在判断逻辑漏洞,所以 JVM 虚拟机没有采用该算法判断 JAVA 对象是否存活;
# 2.2.2 可达性分析算法

Java 虚拟机采用的算法,可达性分析算法;

  • 将一系列 GC Roots 对象作为起点,从这些起点开始向下搜索;
  • 当一个对象 GC Roots 没有任何引用链相连时,则判断对象不可达,可以被 GC 回收;

1.jpg

# 2.3 GC 垃圾回收算法

GC 通过 “可达性分析算法” 标记了垃圾对象以及存活对象,那么该如何将标记的垃圾对象回收呢?

  • 标记 - 清除算法
  • 标记 - 复制算法
  • 标记 - 压缩算法
# 2.3.1 标记 - 清除算法

标记清除算法分为两个阶段:

  • 标记阶段:标记出存活的对象,以及可回收的对象;
  • 清除阶段:回收掉所有被标记的对象;

2.jpg

标记清除算法缺点:

  • 效率不高,因为需要遍历所有的对象,然后标记存活对象,同时还要标记可回收对象;
  • 清除对象后位置不连续,难以找到连续的可用空间,所以容易产生碎片;
# 2.3.2 标记 - 复制算法

算法基本概念:

  • 首先将内存划分为两块相等大小的区域,每次数据存储仅使用其中一块区域;
  • 当这一块区域使用完后,就将还存活的对象复制到另一块区域上面;
  • 最后将使用过的内存空间进行一次清理;

算法工作过程:

3.jpg

标记复制算法优点:

  • 在垃圾对象多的情况下,效率较高;
  • 垃圾清理后,内存无碎片;

标记复制算法缺点:

  • 分配的两块内存区域,在同一时刻,只能使用一半,造成空间让费;
  • 如果创建的对象存活率较高时,需要在两个内存空间来回复制,效率低;
# 2.3.3 标记 - 压缩算法

算法思想:它与 “标记复制” 算法类似,但是标记压缩算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存,因此不会产生内存碎片;

算法工作过程:

4.jpg

# 三、GC 分代垃圾回收算法

由于 JVM 堆空间划分了年轻代和老年代,而且年轻代和老年代中存储对象各有特点,所以不同阶段适合使用不同的 GC 算法进行 GC 回收。

根据回收对象的特点进行选择,在 JVM 中:

  • 年轻代对象存活时间低,适合使用 “复制算法”;
  • 老年代对象存活时间长,适合使用 “标记清除算法、标记压缩算法”;

5.jpg

MinorGC/YoungGC:指年轻代触发 GC 动作

MajorGC/OldGC:指老年代触发 GC 动作

FullGC :年轻代和老年代都没有空间,则全部回收,称之为 FullGC

# 3.1 GC 年轻代垃圾回收 - MinorGC/YoungGC
# 3.1.1 MinorGC/YoungGC 回收阶段 1
  1. 刚刚启动 JVM 时,创建的对象会进入 Eden 区;
  2. 当 Eden 区满了后,如果任然有新的对象需要创建,则会触发一次 MinorGC/YoungGC
    • 通过根可达算法标记垃圾对象;
    • 将活跃的对象通过复制算法,复制到 S0 区域;
    • 然后将 Eden 对象去全部清除;
  3. 清理完成后,Eden 中没有任何对象,S0 中有存活对象,S1 中没有任何对象;

8.jpg

# 3.1.2 MinorGC/YoungGC 回收阶段 2
  1. 当再次创建对象时,对象任然会创建在 Eden 区;
  2. 当 Eden 区空间满了后,会再次触发 GC 垃圾回收;
    • 此时会将 Eden 区的对象复制到 S1 区;
    • 将 S0 存活的对象复制到 S1 区;
  3. 然后清理所有 Eden 区对象及 S0 区对象;

9.jpg

# 3.2 GC 老年代垃圾回收 - MajorGC/OldGC
# 3.2.1 MajorGC/OldGC 回收阶段 1
  • 当有较大对象创建时,可能不会出现在 Eden 区,而是直接进入老年代;
  • 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 + 1,当年龄达到 15(Default)时,升级为老年代;

6.jpg

# 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 就需要熬过漫长的时间,甚至有可能要手动进行垃圾回收;

7.jpg

# 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;

11.jpg

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

1.jpg

# 4.3 串行垃圾收集器 Serial
# 4.3.1 Serial 基本概念
  • 串行垃圾收集器是最基本的、发展历史最悠久的收集器;
  • 特点:单线程、简单高效、对于限定单个 CPU 的环境来说,Serial 收集器由于没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率;
  • 注意:串行垃圾收集器在进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束;
# 4.3.2 Serial 运行示意图

2.jpg

# 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 运行示意图

1.jpg

# 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 运行示意图

3.jpg

# 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 运行示意图

4.jpg

  • 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

1.png

可以自行指定的参数:

-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

此文章已被阅读次数:正在加载...更新于

请我喝[茶]~( ̄▽ ̄)~*

Xu Yong 微信支付

微信支付

Xu Yong 支付宝

支付宝