渣骑 发表于 2025-6-9 18:10:02

JVM(OOM案例)

调优从业务场景开始,没有业务场景的调优都是耍流氓
无监控,不调优
OOM案例1:堆溢出

在 JDK 9 及以上版本中,需要使用 -Xlog 参数来配置 GC 日志的输出格式。以下是修改后的启动参数:
-XX:+PrintGCDetails -XX:MetaspaceSize=64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdump.hprof -Xms200M -Xmx200M -Xlog:gc*,gc+age=trace,safepoint:file=log/gc-oomHeap.log:time,uptime,level,tags:filecount=10,filesize=100M参数说明:

[*]-Xlog:gc*:启用所有 GC 相关的日志。
[*]gc+age=trace:打印对象年龄信息。
[*]safepoint:打印安全点信息。
[*]file=log/gc-oomHeap.log:指定 GC 日志文件路径。
[*]time,uptime,level,tags:在日志中包含时间戳、JVM 启动时间、日志级别和标签。
[*]filecount=10,filesize=100M:日志文件轮转,最多保留 10 个文件,每个文件最大 100MB。
@RestController
@RequestMapping("/api/user")
public class SysUserController {

    @Resource
    private ISysUserService sysUserService;

    /**
   * JVM参数配置
   *
   * 参数配置: 初始-Xms30M -Xmx30M
   * JDK1.8版本使用
   * -XX:+PrintGCDetails -XX:MetaspaceSize=64m
   * -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdump.hprof
   * -XX:+PrintGCDateStamps -Xms200M -Xmx200M -Xloggc:log/gc-oomHeap.log
   */
    @RequestMapping("/add")
    public void add() {
      List<SysUser> users = new ArrayList<>();
      while (true) {
            users.add(new SysUser());
      }
    }

}访问add接口报错:java.lang.OutOfMemoryError: Java heap space,随后在heap文件夹中就会生成heapdump.hprof文件,在log文件夹中就会生成gc-oomHeap.log文件
使用GCeasy网站分析log文件
使用JDK自带的工具分析dump文件

点击红色字体线程查找它的具体代码位置

使用MAT工具分析dump文件

OOM案例2:元空间溢出

jdk8
-XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m
-Xss512K -XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/heapdumpMeta.hprof -XX:SurvivorRatio=8
-XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+PrintGCDateStamps
-Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.logjdk11
-XX:MetaspaceSize=60m
-XX:MaxMetaspaceSize=60m
-Xss512K
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/heapdumpMeta.hprof
-XX:SurvivorRatio=8
-Xms60M
-Xmx60M
-Xlog:gc*,gc+age=trace,safepoint:file=log/gc-oomMeta.log:time,uptime,level,tags:filecount=10,filesize=100M
-Xlog:class+load=info:file=log/class-load.log:time,uptime,level,tags:filecount=10,filesize=100M
-Xlog:class+unload=info:file=log/class-unload.log:time,uptime,level,tags:filecount=10,filesize=100M参数说明:
-Xlog:gc*:替换 -XX:+PrintGCDetails 和 -XX:+PrintGCDateStamps,用于记录所有 GC 相关的日志。
gc+age=trace:打印对象年龄信息。
safepoint:打印安全点信息。
file=log/gc-oomMeta.log:指定 GC 日志文件路径。
time,uptime,level,tags:在日志中包含时间戳、JVM 启动时间、日志级别和标签。
filecount=10,filesize=100M:日志文件轮转,最多保留 10 个文件,每个文件最大 100MB。
-Xlog:class+load:替换 -XX:+TraceClassLoading,用于记录类加载信息。
file=log/class-load.log:指定类加载日志文件路径。
-Xlog:class+unload:替换 -XX:+TraceClassUnloading,用于记录类卸载信息。
file=log/class-unload.log:指定类卸载日志文件路径。
其他参数:
-XX:MetaspaceSize=60m 和 -XX:MaxMetaspaceSize=60m:设置元空间大小。
-Xss512K:设置线程栈大小。
-XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath=heap/heapdumpMeta.hprof:在 OOM 时生成堆转储文件。
-XX:SurvivorRatio=8:设置新生代中 Eden 区与 Survivor 区的比例。
-Xms60M 和 -Xmx60M:设置堆内存的初始大小和最大大小。
    /**
   * -XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m
   * -Xss512K -XX:+HeapDumpOnOutOfMemoryError
   * -XX:HeapDumpPath=heap/heapdumpMeta.hprof -XX:SurvivorRatio=8
   * -XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+PrintGCDateStamps
   * -Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.log
   */
    @RequestMapping("/metaSpaceOom")
    public void metaSpaceOom() {
      ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
      while (true) {
         Enhancer enhance = new Enhancer();
            enhance.setSuperclass(SysUser.class);
            enhance.setUseCache(false);
            enhance.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
                System.out.println("我是加强类,输出print之前的加强方法");
                return methodProxy.invokeSuper(o, objects);
            });
            SysUser sysUser = (SysUser) enhance.create();
      }
    }jps命令
PS D:\Practice> jps
19024 Main
23360 Launcher
23536
10248 Jps
12312 Main
jstat 命令
PS D:\Practice> jstat -gc 123121000 10
S0C    S1C    S0U    S1U      EC       EU      OC         OU       MC   MU    CCSC   CCSU   YGC   YGCT    FGC    FGCT   GCT
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
0.0   4096.00.0   3841.8 30720.010240.0   26624.0    10898.0   38400.0 37090.8 5120.0 4571.6      6    0.024   0      0.000    0.024
这两个命令可以配合使用一下查看线程的JVM内存的使用变化
OOM案例3:GC overhead limit exceeded

java -Xmx10m -XX:+UseParallelGC -XX:+PrintGCDetails GCOverheadLimitExceededExample
public class GCOverheadLimitExceededExample {
    public static void main(String[] args) {
      List<String> list = new ArrayList<>();
      while (true) {
            list.add(new String("GC Overhead Limit Exceeded"));
      }
    }
}分析原因

[*]GC overhead limit exceeded 是Java虚拟机在垃圾回收过程中花费了过多时间(默认超过98%的时间)但只能回收很少的内存(默认少于2%的内存)时抛出的错误。
[*]在这个案例中,我们设置了堆内存的最大值为10MB(-Xmx10m),并且使用并行垃圾回收器(-XX:+UseParallelGC)。由于程序不断地向列表中添加字符串对象,导致堆内存很快被耗尽,垃圾回收器频繁尝试回收内存,但每次只能回收很少的内存,最终触发了GC overhead limit exceeded错误。
OOM案例4:线程溢出

java -Xss1m -Xmx100m ThreadOOMExample
public class ThreadOOMExample {
    public static void main(String[] args) {
      while (true) {
            new Thread(() -> {
                try {
                  Thread.sleep(1000000);
                } catch (InterruptedException e) {
                  e.printStackTrace();
                }
            }).start();
      }
    }
}分析原因

[*]线程溢出 通常是由于创建了过多的线程,导致线程栈内存耗尽。每个线程都需要一定的栈空间(通过-Xss参数设置),当线程数量过多时,栈内存的总需求会超过JVM的可用内存,从而抛出OutOfMemoryError: unable to create new native thread。
[*]在这个案例中,我们设置了每个线程的栈大小为1MB(-Xss1m),并且堆内存为100MB(-Xmx100m)。程序不断地创建新线程,并且每个线程都休眠很长时间,导致线程数量迅速增加,最终耗尽内存。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: JVM(OOM案例)