JVM调优小结

news2024/9/20 12:39:55

JVM常见工具介绍

jinfo(查看配置信息)

查看Java应用程序配置参数或者JVM系统属性,相关命令详情我们可以使用-help或者man命令查看,如下所示:

[root@xxxxxtmp]# jinfo -help
Usage:
    jinfo [option] <pid>
        (to connect to running process)
    jinfo [option] <executable <core>
        (to connect to a core file)
    jinfo [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

where <option> is one of:
    -flag <name>         to print the value of the named VM flag
    -flag [+|-]<name>    to enable or disable the named VM flag
    -flag <name>=<value> to set the named VM flag to the given value
    -flags               to print VM flags
    -sysprops            to print Java system properties
    <no option>          to print both of the above
    -h | -help           to print this help message

为了演示,笔者在服务器上开启了一个Java应用,我们可以使用jps命令查看其进程id,可以看到笔者服务器中有一个pid19946的Java进程。

[root@iZ8vb7bhe4b8nhhhpavhwpZ tmp]# jps
20104 Jps
19946 jar


查看当前应用所有的配置参数以及系统配置属性命令为jinfo pid如下所示:

[root@xxxxx tmp]# jinfo 19946
Attaching to process ID 19946, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08
Java System Properties:

java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.202-b08
sun.boot.library.path = /root/jdk8/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 = /tmp


......

如果我们希望查看当前Java应用是否有配置某些信息,可以使用命令jinfo -flag 配置选项 pid,例如我们想查看当前应用是否有开启gc选项,可以使用下面这段命令

可以看到输出结果为-XX:-PrintGC,因为PrintGC前面是减号,这说明该选项并没有开启。

[root@xxx tmp]# jinfo -flag PrintGC 19946
-XX:-PrintGC

如果我们希望将这个选项开启,我们只需在参数前面加个+号即可,例如我们希望开启gc选项,我们只需键入如下命令

[root@xxxxxtmp]# jinfo -flag +PrintGC 19946

再次查看可以发现,选项生效了

[root@xxxx tmp]# jinfo -flag PrintGC 19946
-XX:+PrintGC

有些参数是键值对的形式,例如我们想配置dump日志的路径,我们也可以使用jinfo进行配置,命令格式为jinfo -flag 参数=值 Java进程id

jinfo -flag HeapDumpPath=/tmp/dump.log 19946

打印JVM选项信息

jinfo -flags 4854
Attaching to process ID 4854, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08
Non-default VM flags: -XX:CICompilerCount=2 -XX:HeapDumpPath=null -XX:InitialHeapSize=33554432 -XX:MaxHeapSize=511705088 -XX:MaxNewSize=170524672 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=11141120 -XX:OldSize=22413312 -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps
Command line:

查看应用属性,命令格式jinfo -sysprops Java进程id

jinfo -sysprops 2341

jmap(查看堆区信息、对象信息等)

jmap作用:

  1. 查看使用的GC算法,堆的配置信息以及各个内存区域的内存使用情况
  2. 显示堆对象的统计信息,包括每一个Java类、对象数量、内存大小、类名称等
  3. 打印等会回收的对象的信息
  4. 生成dump文件,配合jhat使用

查看堆内存使用情况 jmap -heap Java进程id

[root@iZ8vb7bhe4b8nhhhpavhwpZ tmp]# jmap -heap 25534
Attaching to process ID 25534, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 511705088 (488.0MB)
   NewSize                  = 11141120 (10.625MB)
   MaxNewSize               = 170524672 (162.625MB)
   OldSize                  = 22413312 (21.375MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

查看存活的Java对象,命令格式: jmap -histo:live Java进程id

[root@xxxtmp]# jmap -histo:live 25534

 num     #instances         #bytes  class name
----------------------------------------------
   1:         48676        7974952  [C
   2:          7762        1873312  [I
   3:         47785        1146840  java.lang.String
   4:         12737        1120856  java.lang.reflect.Method
   5:          8773         968912  java.lang.Class
   6:         25572         818304  java.util.concurrent.ConcurrentHashMap$Node
   7:         14108         564320  java.util.LinkedHashMap$Entry
   8:          2712         509536  [B
   9:          9308         494936  [Ljava.lang.Object;
  10:          6345         493128  [Ljava.util.HashMap$Node;
  11:          7001         392056  java.util.LinkedHashMap
  12:         11255         360160  java.util.HashMap$Node
  13:         15946         354528  [Ljava.lang.Class;
  14:         18176         290816  java.lang.Object
  15:          3447         248184  java.lang.reflect.Field
  16:           124         192320  [Ljava.util.concurrent.ConcurrentHashMap$Node;

打印正在被回收的类,命令格式:jmap -finalizerinfo Java进程id

[root@xxxtmp]# jmap -finalizerinfo 25534
Attaching to process ID 25534, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08
Number of objects pending for finalization: 0

将存活的对象的信息存到二进制文件中

 jmap -dump:live,format=b,file=/tmp/heap.bin 25534
Dumping heap to /tmp/heap.bin ...
Heap dump file created

此时就可以使用jhat打开该文件,如下所示jhat 文件名,这时候我们就可以通过7000端口查看详情了。

[root@xxxtmp]# jhat heap.bin
Reading from heap.bin...
Dump file created Wed Nov 02 20:21:18 CST 2022
Snapshot read, resolving...
Resolving 346825 objects...
Chasing references, expect 69 dots.....................................................................
Eliminating duplicate references.....................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

jstat(常用,监控运行时状态信息)

jstat用于监控虚拟机各种运行状态信息,显示虚拟机进程中装在、内存、垃圾收集、JIT编译等运行数据。

查看类加载信息,命令格式jstat -class Java进程id

[root@iZ8vb7bhe4b8nhhhpavhwpZ tmp]# jstat -class 2341
Loaded  Bytes  Unloaded  Bytes     Time
  8221 14604.3        1     0.9      12.74
[root@iZ8vb7bhe4b8nhhhpavhwpZ tmp]#

查看编译统计信息jstat -compiler Java进程id


[root@xxxtmp]# jstat -compiler 2341
Compiled Failed Invalid   Time   FailedType FailedMethod
    4177      0       0    17.68          0
[root@iZ8vb7bhe4b8nhhhpavhwpZ tmp]#

查看gc统计信息

[root@iZ8vb7bhe4b8nhhhpavhwpZ tmp]# jstat -gc 2341
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
1408.0 1408.0  0.0   1020.3 11840.0   7493.1   29268.0    22168.5   42840.0 40613.5 5760.0 5311.9     65    0.401   2      0.213    0.613
[root@iZ8vb7bhe4b8nhhhpavhwpZ tmp]#

每个表头的含义如下

1. S0C :年轻代中第一个survivor(幸存区)的容量 (字节)
2. S1C :年轻代中第二个survivor(幸存区)的容量 (字节)
3. S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
4. S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
5. EC:年轻代中Eden(伊甸园)的容量 (字节)
6. EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
7. OC:Old代的容量 (字节)
8. OU:Old代目前已使用空间 (字节)
9. PC:Perm(持久代)的容量 (字节)
10. PU:Perm(持久代)目前已使用空间 (字节)
11. YGC:从应用程序启动到采样时年轻代中gc次数
12. YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
13. FGC:从应用程序启动到采样时old代(全gc)gc次数
14. FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
15. GCT:从应用程序启动到采样时gc用的总时间(s)

查看gc内存容量和元空间容量

[root@xxxtmp]# jstat -gccapacity 2341
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC
 10880.0 166528.0  14656.0 1408.0 1408.0  11840.0    21888.0   333184.0    29268.0    29268.0      0.0 1087488.0  42840.0      0.0 1048576.0   5760.0     65     2

查看年轻代统计信息

[root@xxxxtmp]# jstat -gcnew 2341
 S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT
1408.0 1408.0    0.0 1020.3  2  15  704.0  11840.0   7652.5     65    0.401
[root@iZ8vb7bhe4b8nhhhpavhwpZ tmp]#

参数详情

1. S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
2. S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
3. S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
4. S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
5. TT:持有次数限制
6. MTT:最大持有次数限制
7. EC:年轻代中Eden(伊甸园)的容量 (字节)
8. EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
9. YGC:从应用程序启动到采样时年轻代中gc次数
10. YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)

了解JVM GC日志

获取日志的两种方式

获取GC日志方式大抵有以下两种:

  1. 设定JVM参数,具体的命令参数为:
-XX:+PrintGCDetails # 打印GC日志
-XX:+PrintGCTimeStamps # 打印每一次触发GC时发生的时间
  1. 在服务器上监控:使用jstat查看,如下所示,命令格式为jstat -gc pid 输出间隔时长 输出次数
[root@xxxxx tmp]# jstat -gc 21608 1000 5
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
1088.0 1088.0  0.0   666.5   8832.0   3102.0   21888.0    11286.8   32512.0 30346.5 4352.0 3842.7     31    0.161   1      0.035    0.195
1088.0 1088.0  0.0   666.5   8832.0   3102.0   21888.0    11286.8   32512.0 30346.5 4352.0 3842.7     31    0.161   1      0.035    0.195
1088.0 1088.0  0.0   666.5   8832.0   3102.0   21888.0    11286.8   32512.0 30346.5 4352.0 3842.7     31    0.161   1      0.035    0.195
1088.0 1088.0  0.0   666.5   8832.0   3102.0   21888.0    11286.8   32512.0 30346.5 4352.0 3842.7     31    0.161   1      0.035    0.195
1088.0 1088.0  0.0   666.5   8832.0   3102.0   21888.0    11286.8   32512.0 30346.5 4352.0 3842.7     31    0.161   1      0.035    0.195

常见配置参数解析

对应的JVM参数和含义笔者都注释解释了,读者可自行阅读

-XX:NewSize=5M # 年轻代空间大小
-XX:MaxNewSize=5M # 年轻代最大空间大小
-XX:InitialHeapSize=10M # 初始化堆空间大小
-XX:MaxHeapSize=10M # 最大堆内存
-XX:SurvivorRatio=8 # eden占比,例如这里设置为8,那么eden和S0、S1区总体比例为8:1:1
-XX:PretenureSizeThreshold=10M # 大于这个值的参数直接在老年代分配。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存复制
-XX:+UseParNewGC # 年轻代使用的GC算法
-XX:+UseConcMarkSweepGC # 并行并发CMS垃圾回收器
-XX:+PrintGCDetails  # 打印GC日志详情
-XX:+PrintGCTimeStamps # 打印每次触发GC的时间

由上面的命令我可以知道堆区初始化内存和最大内存为10M,所以我们堆区的总空间为10M
而年轻代的内存为5M,年轻代包含Eden区Survivor区,由参数XX:SurvivorRatio可知Eden区占8/10,所以Eden区为4MSurvivor区总大小为1M,由于Survivor有两个所以每个Survivor区0.5M

在这里插入图片描述

(实践)基于IDEA调试应用程序了解堆JVM日志

为了演示如何查看GC日志,我们不妨写下这样一段代码,我们在计划将JVM年轻代设置为5M,首先创建3M的垃圾,然后创建2M的对象,触发minor GC

编码如下所示:

public class gcTest {
    public static void main(String[] args) {
        //制造3M的垃圾
        byte[] bytes = new byte[1024 * 1024];
        bytes = new byte[1024 * 1024];
        bytes = new byte[1024 * 1024];

        //创建2M的新对象触发GC
        byte[] byte2 = new byte[2 * 1024 * 1024];
    }
}

然后我在IDEA中的JVM option配日志下面这条命令,每一个参数的命令上面都给出了,不记得的读者可以翻到上文中查阅。

-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

在这里插入图片描述

完成后运行代码,可以在控制台输出下面这样一段内容,我们不妨逐行解释一下详情

在这里插入图片描述

首先是第一行,意为0.059s触发年轻代gc,年轻代使用空间从3655K变为512K,而年轻代的总空间为4608K(eden+有对象的s区)

后面3655K->1671K(9728K)则是堆区总空间大小9728K,使用空间由3655K变为1671K

后续Times按顺序即用户、系统、时机耗时时间。

0.059: [GC (Allocation Failure) 0.059: [ParNew: 3655K->512K(4608K), 0.0008750 secs] 3655K->1671K(9728K), 0.0009085 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

接下来这段则是年轻代堆区信息使用情况,可以看到eden大小为4096K,使用了78%。from和to(即s区)同理。

Heap
 par new generation   total 4608K, used 3746K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
  eden space 4096K,  78% used [0x00000000ff600000, 0x00000000ff9289d0, 0x00000000ffa00000)
  from space 512K, 100% used [0x00000000ffa80000, 0x00000000ffb00000, 0x00000000ffb00000)
  to   space 512K,   0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)

这一段以为老年代使用情况

 concurrent mark-sweep generation total 5120K, used 1159K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)

最后两段为元数据和class空间使用情况,不常用到,就不多做解释了。

 Metaspace       used 3159K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 343K, capacity 388K, committed 512K, reserved 1048576K

了解JVM几种GC的区别

  1. Minor GC:发生在年轻代的空间回收,包含edensurvivor,也叫做Young GC
  2. Major GC:在老年代堆区进行空间回收。
  3. Full GC:清理所有堆区的内存空间的垃圾内存,包括年轻代和老年代。

(实践)修复频繁的 Minor GC 和 Major GC

了解频繁Minor GC的危害和原因

造成Minor GC的原因说白了就是年轻代空间太少,当我们的应用程序需要频繁的创建对象时,就会触发Minor GC
频繁的Minor GC会导致服务响应时间变长,而原因也很好理解,由上的介绍我们知道频繁的Minor GC会导致大量的年轻代垃圾被回收,并且会将存活的对象拷贝的老年代,而拷贝这个行为是非常消耗系统性能的。

复现问题

我们的程序编写如下所示,在一个for循环里面,创建3M的垃圾后,再创建2M的数组触发GC

public class gcTest {
    public static void main(String[] args) throws Exception {
        while (true) {
            //制造3M的垃圾
            byte[] bytes = new byte[1024 * 1024];
            bytes = new byte[1024 * 1024];
            bytes = new byte[1024 * 1024];

            //创建2M的新对象触发GC
            byte[] byte2 = new byte[2 * 1024 * 1024];

            Thread.sleep(1000);
        }


    }
}

为了演示年轻代的回收行为,我们需要在对这个应用程序的年轻代堆内存改为5M

-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

输出结果如下,可以看到在1s多就频繁的Minor GC

在这里插入图片描述

修复并解决问题

我们不妨将年轻代和老年代、初始化堆区的空间都扩大,如下所示

-XX:NewSize=10M -XX:MaxNewSize=10M -XX:InitialHeapSize=100M -XX:MaxHeapSize=100M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

输出结果,可以看到发生GC的时间间隔明显长了。

在这里插入图片描述

将年轻代空间调大,是否会更加耗时?

答案是不会的,我们将一次GC的时间拆分为t1t2t1是扫描年轻代空间是否有垃圾的时间,这个时间的几乎可以忽略不计。而t2则是将eden空间存活的对象复制到survivor区的时间,这个复制操作则是t1时间的10倍。
很明显的,JVM的性能瓶颈是t2(拷贝存活对象到s区),所以增加年轻代空间避免没必要的拷贝是正确的做法。

在这里插入图片描述

(实践)频繁的FULL GC

简介

FULL GC的原因即老年区空间满了,需要完完整整走一遍垃圾回收了。注意FULL GC可能会导致整个应用程序阻塞。

(重点)了解CMS GC日志

在进行FULL GC问题实践之前,我们必须了解一下CMS GC的七大阶段:

  1. Initial Mark:首先是初始化标记阶段,这个阶段是STW(stop the world即所有线程停止工作),它会标记出老年代中所有的GC Roots以及被年轻代中存活的对象引用的对象。

在这里插入图片描述

日志范例:

在这里插入图片描述

  1. Concurrent Mark:根据上一个阶段得到的GC Roots,遍历找出整个老年代中存活的对象,注意该阶段是和应用程序线程一起执行的。

在这里插入图片描述

日志范例:

在这里插入图片描述

  1. Concurrent Mark:这个阶段也是和应用程序线程并发工作的,它会将上一阶段运行期间引用关系发生变化的对象标记为Dirty Card

在这里插入图片描述

  1. Concurrent Abortable Preclean:该阶段也是和应用程序线程并发执行的,它会尝试着去承担后续STWFinal Remark阶段的工作。

日志范例:

在这里插入图片描述

  1. Final Remark:这个阶段是STW的,会标记出老年代中所有存活的对象。

日志范例

在这里插入图片描述

  1. Concurrent Sweep:和应用程序线程同时进行,移除那些不需要的对象,释放空间。

在这里插入图片描述

日志范例:

在这里插入图片描述

  1. Concurrent Reset:这个阶段并发执行,重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用。

日志范例:

在这里插入图片描述

问题复现

我们现在模拟一个场景,我们的web应用中有一个定时任务,这个定时任务每隔1s会想另一个定时任务线程池中提交100个任务。

注意spring boot应用要想开启定时任务必须添加@EnableScheduling注解。

代码如下所示:

@Component
public class Task {
    private static Logger logger = LoggerFactory.getLogger(Task.class);


    private static final ScheduledThreadPoolExecutor executor =
            new ScheduledThreadPoolExecutor(50,
                    new ThreadPoolExecutor.DiscardOldestPolicy());

	
    private static class Obj {
        private String name = "name";
        private int age = 18;
        private String gender = "man";
        private LocalDate birthday = LocalDate.MAX;

        public void func() {
            //这个方法什么也不做
        }

		//返回count个Obj对象
        private static List<Obj> getObjList(int count) {

            List<Obj> objList = new ArrayList<>(count);

            for (int i = 0; i != count; ++i) {
                objList.add(new Obj());
            }
            return objList;
        }
    }

    @Scheduled(cron = "0/1 * *  * * ? ")   //每1秒执行一次
    public void execute() {
        logger.info("1s一次定时任务");
        //向线程池提交100个任务
        Obj.getObjList(100).forEach(i -> executor.scheduleWithFixedDelay(
                i::func, 2, 3, TimeUnit.SECONDS
        ));
    }
}

完成后我们设置下面这段JVM参数后,将其启动

-Xms20M -Xmx20M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

不久后,控制台出现大量的CMS GC日志:

1288.111: [GC (CMS Initial Mark) [1 CMS-initial-mark: 13695K(13696K)] 19836K(19840K), 0.0058731 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
1288.117: [CMS-concurrent-mark-start]
1288.130: [CMS-concurrent-mark: 0.013/0.013 secs] [Times: user=0.05 sys=0.02, real=0.01 secs] 
1288.130: [CMS-concurrent-preclean-start]
1288.133: [Full GC (Allocation Failure) 1288.133: [CMS1288.142: [CMS-concurrent-preclean: 0.012/0.012 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
 (concurrent mode failure): 13695K->13695K(13696K), 0.0610050 secs] 19839K->19836K(19840K), [Metaspace: 29026K->29026K(1077248K)], 0.0610521 secs] [Times: user=0.06 sys=0.00, real=0.06 secs] 
1288.258: [Full GC (Allocation Failure) 1288.258: [CMS: 13695K->13695K(13696K), 0.0612134 secs] 19839K->19836K(19840K), [Metaspace: 29026K->29026K(1077248K)], 0.0612676 secs] [Times: user=0.06 sys=0.00, real=0.06 secs] 
1288.320: [GC (CMS Initial Mark) [1 CMS-initial-mark: 13695K(13696K)] 19836K(19840K), 0.0041303 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
1288.324: [CMS-concurrent-mark-start]
1288.339: [CMS-concurrent-mark: 0.015/0.015 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
1288.339: [CMS-concurrent-preclean-start]
1288.348: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
1288.348: [CMS-concurrent-abortable-preclean-start]
1288.348: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
1288.348: [GC (CMS Final Remark) [YG occupancy: 6142 K (6144 K)]1288.348: [Rescan (parallel) , 0.0038030 secs]1288.352: [weak refs processing, 0.0000311 secs]1288.352: [class unloading, 0.0024908 secs]1288.355: [scrub symbol table, 0.0042932 secs]1288.359: [scrub string table, 0.0002748 secs][1 CMS-remark: 13695K(13696K)] 19838K(19840K), 0.0109811 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
1288.360: [CMS-concurrent-sweep-start]
1288.364: [CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
1288.364: [CMS-concurrent-reset-start]
1288.364: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
1288.384: [GC (Allocation Failure) 1288.384: [ParNew: 6143K->6143K(6144K), 0.0000365 secs]1288.384: [CMS: 13695K->13695K(13696K), 0.0540105 secs] 19839K->19836K(19840K), [Metaspace: 29026K->29026K(1077248K)], 0.0541242 secs] [Times: user=0.05 sys=0.00, real=0.05 secs] 

排查思路

随着时间的推移,我们发现控制台出现了大量FULL GC(如果是生产环境,可能服务的响应速度会非常慢),所以我们首先需要定位这个进程的pid,首先在控制台输入jps找到进程,输出如下结果,可以看到这个pid7476


10848 KotlinCompileDaemon
4832 Jps
7476 JstackTestApplication
8856 RemoteMavenServer


然后我们使用jstat观察其gc情况,如下所示,可以看到每隔10s,就会增加大量的FULL GC

C:\Users\xxx>jstat -gc 7476 10000 10
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
640.0  640.0   0.0   640.0   5504.0   665.5    13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  15      0.100    0.184
640.0  640.0   0.0   640.0   5504.0   1487.2   13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  25      0.142    0.227
640.0  640.0   0.0   640.0   5504.0   1697.8   13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  35      0.185    0.269
640.0  640.0   0.0   640.0   5504.0   2380.4   13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  41      0.249    0.334
640.0  640.0   0.0   640.0   5504.0   3063.0   13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  44      0.260    0.345
640.0  640.0   0.0   640.0   5504.0   3745.6   13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  46      0.270    0.355
640.0  640.0   0.0   640.0   5504.0   4342.6   13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  50      0.297    0.382

再查看jmap查看堆区使用情况

 jmap -heap 7476 
Attaching to process ID 26176, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.212-b10

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 20971520 (20.0MB)
   NewSize                  = 6946816 (6.625MB)
   MaxNewSize               = 6946816 (6.625MB)
   OldSize                  = 14024704 (13.375MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 6291456 (6.0MB)
   used     = 5088288 (4.852569580078125MB)
   free     = 1203168 (1.147430419921875MB)
   80.87615966796875% used
Eden Space:
   capacity = 5636096 (5.375MB)
   used     = 5088288 (4.852569580078125MB)
   free     = 547808 (0.522430419921875MB)
   90.28036428052326% used
From Space:
   capacity = 655360 (0.625MB)
   used     = 0 (0.0MB)
   free     = 655360 (0.625MB)
   0.0% used
To Space:
   capacity = 655360 (0.625MB)
   used     = 0 (0.0MB)
   free     = 655360 (0.625MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 14024704 (13.375MB)
   used     = 13819664 (13.179458618164062MB)
   free     = 205040 (0.1955413818359375MB)
   98.53800836010514% used

12064 interned Strings occupying 1120288 bytes.

在排除内存泄漏的问题后,我们通过jstack定位进程中导致是什么对象导致老年代堆区被大量占用,如下所示可以看到前20名中的对象都是和定时任务相关,有一个Task$Obj对象非常抢眼,很明显就是因为它的数量过多导致的,此时我们就可以通过定位代码确定如何解决,常见方案无非是: 优化代码、增加空间两种方式,一般来说我们都会采用代码优化的方式去解决。

$ jmap -histo 7476 | head -n 20

 num     #instances         #bytes  class name
----------------------------------------------
   1:         50760        3654720  java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask
   2:         30799        2901552  [C
   3:         88986        2847552  java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
   4:         50700        1622400  com.example.jstackTest.Task$Obj
   5:         50760        1218240  java.util.concurrent.Executors$RunnableAdapter
   6:         50700         811200  com.example.jstackTest.Task$$Lambda$587/1605553313
   7:          6391         707928  java.lang.Class
   8:         29256         702144  java.lang.String
   9:         13577         434464  java.util.concurrent.ConcurrentHashMap$Node
  10:          6363         341016  [Ljava.lang.Object;
  11:          1722         312440  [B
  12:          3414         230424  [I
  13:             4         210680  [Ljava.util.concurrent.RunnableScheduledFuture;
  14:          5223         208920  java.util.LinkedHashMap$Entry
  15:          2297         202136  java.lang.reflect.Method
  16:          2262         193760  [Ljava.util.HashMap$Node;
  17:          5668         181376  java.util.HashMap$Node

而本次问题也很明显,任务是一个个提交到定时任务线程池中,是由于定时任务队列DelayedWorkQueue不断堆积任务导致内存被打满。所以最终改成将一个批操作一次性提交到定时任务中得以解决

 @Scheduled(cron = "0/1 * *  * * ? ")   //每1秒执行一次
    public void execute() {
        logger.info("1s一次定时任务");
        //向线程池提交100个任务


        executor.scheduleWithFixedDelay(() -> {
                    Obj.getObjList(100).forEach(i -> i.func());
                }, 2, 3, TimeUnit.SECONDS
        );


    }

补充:导致频繁FULL GC三大原因

我们需要需要具体分析才能得出解决方案,总的来说原因可以分为3个:

  1. 用户频繁调用System.gc():这种情况需要修改代码即可,我们不该频繁调用这个方法的。
  2. 老年区空间过小:视情况适当扩大空间。
  3. 大对象过多:这种情况视情况决定是扩大老年代空间或者将大对象拆分。

更多JVM调优

从实际案例聊聊Java应用的GC优化

Java中9种常见的CMS GC问题分析与解决

听说 JVM 性能优化很难?今天我小试了一把!

一次线上JVM调优实践,FullGC40次/天到10天一次的优化过程

线上频繁发生Full GC 如何调优?如何快速定位OOM、cpu飙升、线程死锁等问题

参考文献

JVM学习(7)Stop-The-World

JVM调优——之CMS GC日志分析

从实际案例聊聊Java应用的GC优化

Spring Boot定时任务详解(线程池方式)

SpringBoot使用@Scheduled注解实现定时任务

面渣逆袭(Java 虚拟机-JVM面试题八股文)必看👍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1325976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《信息安全工程师教材》-蒋建春、信息安全完全参考手册-Mark Rhodes Ousley、CISSP官方学习指南第九版、ISO27002学习

文章目录 介绍关系学习顺序重复内容学习方法建议学习时间可能重合的部分更详细的学习计划 介绍 《信息安全工程师教材》- 蒋建春&#xff1a;这本教材可能是针对中国地区信息安全工程师的认证考试而编写的&#xff0c;它可能会涵盖信息安全的基本概念、技术和政策&#xff0c;特…

Ubuntu 常用命令之 gzip 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 gzip 是一个在 Linux 和 Unix 系统中常用的文件压缩工具。它的名字来源于 GNU zip&#xff0c;作为一个自由软件&#xff0c;它是 GNU 项目的一部分。gzip 命令通常用于压缩文件&#xff0c;以节省磁盘空间&#xff0c;或者减小文…

Python教程:对于初学者,几个易懂的装饰器示例用法

装饰器是Python中的一个高级功能&#xff0c;它可以用来扩展或修改一个函数或方法的功能&#xff0c;而不需要修改其原始代码。装饰器本质上是一个函数&#xff0c;它接受一个函数作为参数&#xff0c;并返回一个新的函数对象。 装饰器通常用于添加与函数功能无关的额外功能&a…

泛微OA C# 调用 WebAPI功能实现

泛微OA C# 调用 WebAPI功能实现 OA 在线文档地址1. 创建流程字段参数 mainData 简单说明字段表明细表2. 接口封装2.1 接口初始化2.2 接口注册2.3 获取Token2.4 拼装 Headers2.5 常用工作流方法2.5.1 创建2.5.2 删除2.5.3 撤回2.5.4 退回3. 接口调用OA 在线文档地址 Token认证 …

【C#】.net core 6.0 通过依赖注入注册和使用上下文服务

给自己一个目标&#xff0c;然后坚持一段时间&#xff0c;总会有收获和感悟&#xff01; 请求上下文是指在 Web 应用程序中处理请求时&#xff0c;包含有关当前请求的各种信息的对象。这些信息包括请求的头部、身体、查询字符串、路由数据、用户身份验证信息以及其他与请求相关…

Windows如何安装使用TortoiseSVN客户端并实现公网访问本地SVN Server

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统&#xff0c;它与Apache Subversion&#xff08;SVN&#xff09;集成在一起&#xff0c;提供了一个用户友好的界面&#xff0c;方便用…

一文吃透String

1.概览 String 被声明为 final&#xff0c;因此它不可被继承。 内部使用 char 数组存储数据&#xff0c;该数组被声明为 final&#xff0c;这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法&#xff0c;因此可以保证 String 不…

苏宁易购商品详情API:电商实时数据

一、引言 在当前的电商行业中&#xff0c;数据是最为宝贵的资源之一。如何获取实时、准确的数据&#xff0c;对于电商业务的运营和优化至关重要。作为中国领先的电商平台之一&#xff0c;苏宁易购提供了丰富的API接口&#xff0c;其中包括商品详情API&#xff0c;以便第三方开…

AI文生图功能试用

使用边界AICHAT中的文生图功能&#xff0c;使用下面的文本描述&#xff1a; 春天&#xff0c;在大明湖畔&#xff0c;一个中国南方的&#xff0c;女人&#xff0c;皮肤白皙&#xff0c;长发飘逸&#xff0c;明亮眼睛&#xff0c;五官俊俏&#xff0c;在静静的临摹&#xff0c;…

【扩散模型】8、DALL-E2 | 借助 CLIP 的图文对齐能力来实现文本到图像的生成

文章目录 一、背景二、方法2.1 Decoder2.2 Prior 三、图像控制3.1 Variations3.2 Interpolations3.3 Text Diffs 四、探索 CLIP 的潜在空间五、文本到图像的生成5.1 先验的重要性5.2 人类评价5.3 多样性和保真性的平衡5.3 在 COCO 上对比 论文&#xff1a;DALLE.2 代码&#x…

PyQt6 QColorDialog颜色对话框控件

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计50条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

在ClickHouse数据库中启用预测功能

在这篇博文中&#xff0c;我们将介绍如何将机器学习支持的预测功能与 ClickHouse 数据库集成。ClickHouse 是一个快速、开源、面向列的 SQL 数据库&#xff0c;对于数据分析和实时分析非常有用。该项目由 ClickHouse&#xff0c; Inc. 维护和支持。我们将探索它在需要数据准备以…

程序员的23大IONIO面试问题及答案

文章目录 1. 什么是IO流&#xff1f;2.java中有几种类型的流&#xff1f;3.字节流和字符流哪个好&#xff1f;怎么选择&#xff1f;4.读取数据量大的文件时&#xff0c;速度会很慢&#xff0c;如何选择流&#xff1f;5. IO模型有几种&#xff1f;6.阻塞IO &#xff08;blocking…

c jpeg 理论霍夫曼 DC AC表,c程序实现正向逆向转换

此4张表是理论表&#xff0c;不是针对某张图片的特定表。如编码程序不统计生成某图片的专用霍夫曼表&#xff0c;应该也可用理论表代用编码。 1.亮度DC表 左边第一列是二进制位数&#xff0c;就是对此位数编码 中间一列是生成比特流的位数&#xff0c;右边是生成的比特流。 …

Thunderbolt 3 PCIe Expansion 扩展卡

计算机目前大部分都能够提供 Thunderbolt 3 接口了。 Thunderbolt 3 的传输速度更快&#xff0c;所以我们需要把 Thunderbolt 3 转换为 SAS HBA&#xff0c;但市场上没有这个转换设备。 后来我们发现有 Thunderbolt 3 PCIe Expansion&#xff0c;就是通过这个设备把 Thunderb…

Postgresql中PL/pgSQL的游标、自定义函数、存储过程的使用

场景 Postgresql中PL/pgSQL代码块的语法与使用-声明与赋值、IF语句、CASE语句、循环语句&#xff1a; Postgresql中PL/pgSQL代码块的语法与使用-声明与赋值、IF语句、CASE语句、循环语句-CSDN博客 上面讲了基本语法&#xff0c;下面记录游标、自定义函数、存储过程的使用。 …

(企业 / 公司项目)代码生成器底层原理:模板框架freemarker

1.按照设置好的模板文件就能生成Java&#xff0c;vue文件&#xff0c;前后端都可生成。 2.也可以进行复杂Excel到处&#xff1a;可以转成xml&#xff0c;用xml来制作模板&#xff0c;在生成excel 3.需要批量生成格式固定的一类文件的需求也可以使用模板框架freemarker 首先引…

大数据时代,如何基于机密虚拟化技术构建数据安全的“基石”

云布道师 2023 年 10 月 31 日-11 月 2 日&#xff0c;2023 云栖大会在中国杭州云栖小镇举行&#xff0c;阿里云弹性计算产品专家唐湘华、阿里云高级安全专家刘煜堃、蚂蚁集团高级技术专家肖俊贤三位嘉宾在【云服务器 & 计算服务】专场中共同带来题为《大数据时代&#xf…

推荐几个好用的开源无代码/低代码开发平台

一、什么是无代码/低代码开发 无代码/低代码开发是一种可视化的应用程序开发方法&#xff0c;使用具有拖放组件和模型驱动逻辑组合的图形界面。无代码/低代码开发试图降低从软件技术平台、产品和服务中提取价值的进入壁垒。低代码开发平台被称为可视化集成开发环境&#xff08…

任意文件下载漏洞的利用思考

0x01 前言 任意文件下载漏洞作为最常见的WEB漏洞之一&#xff0c;在平常的渗透测试中经常遇到&#xff0c;但是很多人却并没有深入去想该如何利用这种漏洞&#xff0c;导致忽略了一些细节的信息。 0x02 传统利用 1&#xff09; 下载配置文件连数据库 通过任意文件下载漏洞下载网…