Java JFR 民间指南 - 事件详解 - jdk.ThreadAllocationStatistics

本文详细介绍了Java11及以上版本引入的jdk.ThreadAllocationStatistics事件,用于统计每个线程的对象分配情况,帮助诊断内存问题。文章通过一个测试示例展示了如何使用此事件,并分析了事件的实现原理,指出其对性能影响较小。此外,还探讨了默认配置的不足,建议调整为更频繁的采集周期以满足特定需求。

定时线程分配统计事件:jdk.ThreadAllocationStatistics
引入版本:Java 11

相关 ISSUES:

1.Test jdk/jfr/event/runtime/TestThreadAllocationEvent.java fails with null thread :在某些情况下,jdk.ThreadAllocationStatistics 没有采集到 JFR 相关线程,导致空指针。这个 Bug 和 jdk/jfr/event/compiler/TestCompilerCompile.java failed due to “RuntimeException: No thread in event” 重复.该 BUG 对于使用没有大影响,于 Java 16 修复。

2.Per thread IO statistics in JFR ,这是一个新特性,期望和 ThreadAllocationStatistics 事件类似,提供每个线程的 IO 数据统计,这个特性还没有通过讨论。

各版本配置:
从 Java 11 引入之后没有改变过:

采样配置( profile.jfc of Java 11 , profile.jfc of Java 12 , profile.jfc of Java 13 , profile.jfc of Java 14 , profile.jfc of Java 15 , profile.jfc of Java 16 , profile.jfc of Java 17 ):


为何需要这个事件?
Java 中业务线程都会分配对象,对于以下关键的业务,对象的分配可能更加频繁。有时候我们可能会遇到以下两个情况:

1.线上应用频繁 GC,可能是先开始 Young GC(New GC,Light GC,不同 GC 叫法不一样),之后伴随着 Old GC 或者 Full GC,可以观察到线上应用占用 CPU 高的线程也是 GC 线程。 这个很可能是某个业务 BUG 拉取了大量的数据 ,例如查询数据库没加条件导致查询整个表,三方接口没限制 limit 返回了超级多的数据。想要直观快速的定位这个问题,可以通 过观察那些线程突然分配了很多内存 ,然后查看这些线程的堆栈进一步确认。 jdk.ThreadAllocationStatistics 就是用来查看这个的 。

2.线上 GC 变多,想要减少 GC,光看 jmap 对象统计太多抽象,并不直观知道是那些代码创建的这些对象。堆 dump 太重,对于大内存进程分析成本也很大。可以通过 jdk.ThreadAllocationStatistics 查看是那些线程分配对象比较多,采集这些线程的堆栈可以定位相关代码。

事件包含属性

使用代码测试这个事件

package com.github.hashjang.jfr.test;

import com.sun.management.ThreadMXBean;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedThread;
import jdk.jfr.consumer.RecordingFile;
import sun.hotspot.WhiteBox;

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class TestThreadAllocationStatistics {
    private static String EVENT_NAME = "jdk.ThreadAllocationStatistics";
    //50ms采集一次
    private static long eventPeriodMillis = 50;
    //对于字节数组对象头占用16字节
    private static final int BYTE_ARRAY_OVERHEAD = 16;
    //我们要测试的对象大小是1kb
    private static final int OBJECT_SIZE = 1024;

    public static void main(String[] args) throws IOException {

        Recording recording = new Recording();
        recording.enable(EVENT_NAME).withPeriod(Duration.ofMillis(eventPeriodMillis));
        recording.start();
        //使用 WhiteBox 执行 FullGC,清除干扰
        WhiteBox whiteBox = WhiteBox.getWhiteBox();
        whiteBox.fullGC();

        Allocator allocators[] = new Allocator[4];
        CountDownLatch allocationsDoneLatch = new CountDownLatch(allocators.length);
        for (int i = 0; i < allocators.length; i++) {
            allocators[i] = new Allocator(allocationsDoneLatch, OBJECT_SIZE * (i + 1) - BYTE_ARRAY_OVERHEAD);
            allocators[i].setDaemon(true);
            allocators[i].start();
        }
        Map<Long, Allocator> map = Arrays.stream(allocators).collect(Collectors.toMap(Thread::getId, v -> v));
        Map<Long, Long> threadAllocatedMap = new HashMap<>();
        try {
            allocationsDoneLatch.await();
            //再等待一段时间让定时采集 jdk.ThreadAllocationStatistics 采集到最新的
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        recording.stop();
        Path path = new File(new File(".").getAbsolutePath(), "recording-" + recording.getId() + "-pid" + ProcessHandle.current().pid() + ".jfr").toPath();
        recording.dump(path);
        for (RecordedEvent event : RecordingFile.readAllEvents(path)) {
            if (!EVENT_NAME.equals(event.getEventType().getName())) {
                continue;
            }
            long javaThreadId = ((RecordedThread) event.getValue("thread")).getJavaThreadId();
            if (map.containsKey(javaThreadId)) {
                //事件是时间有序的(采集时间比较短),所以放入的最后一个就是采集到的线程最终的分配大小
                threadAllocatedMap.put(javaThreadId, event.getLong("allocated"));
                System.out.println(event);
            }
        }

        map.forEach((id, thread) -> {
            System.out.println("Thread " + id + " allocated(from JMX): " + thread.totalAllocated + "; allocated(from jdk.ThreadAllocationStatistics): " + threadAllocatedMap.get(id));
        });
    }

    public static class Allocator extends Thread {
        private volatile long totalAllocated = -1;
        private final int allocationSize;
        public byte[] buffer;
        private final CountDownLatch allocationsDoneLatch;

        public Allocator(CountDownLatch allocationsDoneLatch, int allocationSize) {
            this.allocationsDoneLatch = allocationsDoneLatch;
            this.allocationSize = allocationSize;
        }

        @Override
        public void run() {
            for (int batches = 0; batches < 100; batches++) {
                for (int i = 0; i < 1024; i++) {
                    buffer = new byte[allocationSize];
                }
                try {
                    //期望每个采集周期之间的分配次数为 5
                    Thread.sleep(eventPeriodMillis / 5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //获取当前线程分配的对象大小
            totalAllocated = getThreadAllocatedBytes();
            allocationsDoneLatch.countDown();
            //设置线程为守护线程,等待主线程结束之后会自动结束
            //这里进入死循环是因为防止定时采集 jdk.ThreadAllocationStatistics 事件的时候采集不到
            while (true) {
                Thread.yield();
            }
        }

        private long getThreadAllocatedBytes() {
            ThreadMXBean bean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
            return bean.getThreadAllocatedBytes(Thread.currentThread().getId());
        }
    }
}

使用以下 JVM 参数启动:

-Xbootclasspath/a:D:\github\jfr-spring-all\jdk-white-box\target\jdk-white-box-17.0-SNAPSHOT.jar  -Xms512m -Xmx512m  -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xlog:gc

输出结果:

[0.016s][info][gc] Using G1
[0.679s][info][gc] GC(0) Pause Full (WhiteBox Initiated Full GC) 19M->2M(512M) 8.804ms
[0.714s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 27M->2M(512M) 1.309ms
[1.099s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 308M->2M(512M) 0.943ms
[1.445s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 308M->2M(512M) 1.141ms
[1.788s][info][gc] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 308M->2M(512M) 1.117ms
jdk.ThreadAllocationStatistics {
  startTime = 12:41:29.915
  allocated = 1.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:29.915
  allocated = 2.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:29.915
  allocated = 3.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:29.915
  allocated = 4.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:29.965
  allocated = 6.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:29.965
  allocated = 12.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:29.965
  allocated = 15.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:29.965
  allocated = 20.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.015
  allocated = 10.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.015
  allocated = 20.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.015
  allocated = 27.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.015
  allocated = 36.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.066
  allocated = 15.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.066
  allocated = 28.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.066
  allocated = 39.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.066
  allocated = 50.3 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.116
  allocated = 19.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.116
  allocated = 36.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.116
  allocated = 51.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.116
  allocated = 64.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.167
  allocated = 24.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.167
  allocated = 44.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.167
  allocated = 63.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.167
  allocated = 80.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.217
  allocated = 28.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.217
  allocated = 52.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.217
  allocated = 75.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.217
  allocated = 96.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.268
  allocated = 32.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.268
  allocated = 61.3 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.268
  allocated = 87.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.268
  allocated = 109.5 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.319
  allocated = 37.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.319
  allocated = 68.3 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.319
  allocated = 99.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.319
  allocated = 124.3 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.370
  allocated = 41.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.370
  allocated = 78.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.370
  allocated = 111.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.370
  allocated = 144.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.420
  allocated = 45.4 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.420
  allocated = 86.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.420
  allocated = 126.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.420
  allocated = 160.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.470
  allocated = 50.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.470
  allocated = 96.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.470
  allocated = 138.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.470
  allocated = 180.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.520
  allocated = 54.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.520
  allocated = 104.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.520
  allocated = 150.4 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.520
  allocated = 196.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.571
  allocated = 58.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.571
  allocated = 112.3 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.571
  allocated = 165.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.571
  allocated = 212.5 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.621
  allocated = 63.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.621
  allocated = 122.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.621
  allocated = 180.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.621
  allocated = 232.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.671
  allocated = 67.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.671
  allocated = 130.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.671
  allocated = 192.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.671
  allocated = 248.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.722
  allocated = 72.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.722
  allocated = 140.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.722
  allocated = 207.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.722
  allocated = 268.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.772
  allocated = 76.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.772
  allocated = 148.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.772
  allocated = 219.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.772
  allocated = 284.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.581
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.581
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.581
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.581
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.631
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.631
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.631
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.631
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.682
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.682
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.682
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.682
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.732
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.732
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.732
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.732
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.823
  allocated = 81.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.823
  allocated = 158.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.823
  allocated = 234.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.823
  allocated = 304.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.874
  allocated = 85.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.874
  allocated = 166.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.874
  allocated = 246.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.874
  allocated = 320.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.925
  allocated = 90.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.925
  allocated = 176.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.925
  allocated = 258.5 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.925
  allocated = 340.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.975
  allocated = 94.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.975
  allocated = 184.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.975
  allocated = 273.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:30.975
  allocated = 356.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.024
  allocated = 99.0 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.024
  allocated = 194.0 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.024
  allocated = 285.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.024
  allocated = 372.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.075
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.075
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.075
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.075
  allocated = 392.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.126
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.126
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.126
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.126
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.177
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.177
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.177
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.177
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.228
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.228
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.228
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.228
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.278
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.278
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.278
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.278
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.329
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.329
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.329
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.329
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.379
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.379
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.379
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.379
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.429
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.429
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.429
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.429
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.479
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.479
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.479
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.479
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.530
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.530
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.530
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.530
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.791
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.791
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.791
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.791
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.842
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.842
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.842
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.842
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.892
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.892
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.892
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.892
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.943
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.943
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.943
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.943
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.993
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.993
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.993
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:31.993
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:32.043
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:32.043
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:32.043
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:32.043
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:32.093
  allocated = 100.3 MB
  thread = "Thread-0" (javaThreadId = 27)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:32.093
  allocated = 200.1 MB
  thread = "Thread-1" (javaThreadId = 28)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:32.093
  allocated = 300.0 MB
  thread = "Thread-2" (javaThreadId = 29)
}


jdk.ThreadAllocationStatistics {
  startTime = 12:41:32.093
  allocated = 400.0 MB
  thread = "Thread-3" (javaThreadId = 30)
}


Thread 27 allocated(from JMX): 105179160; allocated(from jdk.ThreadAllocationStatistics): 105179160
Thread 28 allocated(from JMX): 209780040; allocated(from jdk.ThreadAllocationStatistics): 209780040
Thread 29 allocated(from JMX): 314573616; allocated(from jdk.ThreadAllocationStatistics): 314573616
Thread 30 allocated(from JMX): 419431216; allocated(from jdk.ThreadAllocationStatistics): 419431216

底层原理以及相关 JVM 源码

每隔配置的间隔,就会调用 requestThreadAllocationStatistics(void) 方法采集每一个线程的分配信息:

jfrPeriodic.cpp

#define TRACE_REQUEST_FUNC(id)    void JfrPeriodicEventSet::request##id(void)

//使用了宏定义函数,实际函数就是 requestThreadAllocationStatistics(void)
TRACE_REQUEST_FUNC(ThreadAllocationStatistics) {
  ResourceMark rm;
  //获取线程数量,虽然后面线程数量在采集过程中可能会改变,利用这个值初始化数组
  int initial_size = Threads::number_of_threads();
  //建立一个数组记录每个线程的分配大小
  GrowableArray<jlong> allocated(initial_size);
  //建立一个数组记录线程号,和上面那个数组一一对应
  GrowableArray<traceid> thread_ids(initial_size);
  //记录当前时间
  JfrTicks time_stamp = JfrTicks::now();
  //新建一个 JfrJavaThreadIterator 来遍历每个线程
  JfrJavaThreadIterator iter;
  while (iter.has_next()) {
    JavaThread* const jt = iter.next();
    assert(jt != NULL, "invariant");
    //读取每个线程的 cooked_allocated_bytes()
    allocated.append(jt->cooked_allocated_bytes());
    //记录线程号(包括系统线程号以及 Java 线程号)
    thread_ids.append(JFR_THREAD_ID(jt));
  }

  //遍历数组,生成 JFR 事件并采集
  for(int i = 0; i < thread_ids.length(); i++) {
    EventThreadAllocationStatistics event(UNTIMED);
    //设置当前线程已分配大小
    event.set_allocated(allocated.at(i));
    //设置线程信息
    event.set_thread(thread_ids.at(i));
    //设置结束时间
    event.set_endtime(time_stamp);
    event.commit();
  }
}

统计线程的分配内存的大小方法即 cooked_allocated_bytes() 方法的源码如下:

thread.inline.hpp

inline jlong Thread::cooked_allocated_bytes() {
  //原子读取 _allocated_bytes
  jlong allocated_bytes = Atomic::load_acquire(&_allocated_bytes);
  //如果开启了 TLAB(默认开启),则需要查看下当前线程的 TLAB 已经分配的对象大小
  if (UseTLAB) {
    //统计当前线程的 TLAB 已经分配的大小
    size_t used_bytes = tlab().used_bytes();
    //如果当前 TLAB 在初始化,或者全局 TLAB 在初始化,会发生 used_bytes > max_size 的情况,忽略这种情况。
    if (used_bytes <= ThreadLocalAllocBuffer::max_size_in_bytes()) {
      //这样统计其实有可能有点问题,即发生 TLAB retire 的时候,_allocated_bytes 会加上 used_bytes 之后申请一个新的 TLAB,这时候调用这个方法可能会把这个 used_bytes 加了两遍
      return allocated_bytes + used_bytes;
    }
  }
  return allocated_bytes;
}

那么哪里会修改 _allocated_bytes 这个变量呢?有两个地方,一个是发生 TLAB 外分配的时候,另一个是发生 TLAB retire 的时候。 TLAB retire 是在发生 GC 以及 TLAB 满了并且剩余空间小于当前最大浪费空间限制的时候会重新申请一个新的 TLAB 进行分配,申请新的之前需要将当前 TLAB retire。

发生 TLAB 外分配修改 _allocated_bytes 对应源码:

memAllocator.cpp

//如果是 TLAB 外分配,会调用这个方法
HeapWord* MemAllocator::allocate_outside_tlab(Allocation& allocation) const {
  //标记当前分配为 TLAB 外分配
  allocation._allocated_outside_tlab = true;
  //执行 TLAB 外分配,不同 GC 不同
  HeapWord* mem = Universe::heap()->mem_allocate(_word_size, &allocation._overhead_limit_exceeded);
  if (mem == NULL) {
    return mem;
  }

  NOT_PRODUCT(Universe::heap()->check_for_non_bad_heap_word_value(mem, _word_size));
  //计算当前分配的字节大小
  size_t size_in_bytes = _word_size * HeapWordSize;
  //增加 _allocated_bytes 当前分配的字节大小
  _thread->incr_allocated_bytes(size_in_bytes);

  return mem;
}

TLAB retire 对应源码:

threadLocalAllocBuffer.cpp

void ThreadLocalAllocBuffer::retire(ThreadLocalAllocStats* stats) {
  if (stats != NULL) {
    accumulate_and_reset_statistics(stats);
  }

  if (end() != NULL) {
    invariants();
    //将当前 TLAB 使用的字节数加到 _allocated_bytes
    //从这里可以看出,如果这个方法和 cooked_allocated_bytes() 同时被调用,可能 used_bytes 被加了两遍。
    thread()->incr_allocated_bytes(used_bytes());
    insert_filler();
    initialize(NULL, NULL, NULL);
  }
}

我们再来看看 JMX 中查看线程分配内存大小的具体实现。首先对应的 JMX 的代码是:

//我们示例中底层调用的实际就是这个方法获取线程分配的内存大小
protected long getThreadAllocatedBytes(long id) {
    boolean verified = verifyThreadAllocatedMemory(id);

    if (verified) {
        //调用 getThreadAllocatedMemory0 获取分配的内存大小
        return getThreadAllocatedMemory0(
            Thread.currentThread().getId() == id ? 0 : id);
    }
    return -1;
}

//getThreadAllocatedMemory0 是一个 native 方法
private static native long getThreadAllocatedMemory0(long id);

这个 native 方法对应的 JVM 源码是:

ThreadImpl.c

JNIEXPORT jlong JNICALL
Java_sun_management_ThreadImpl_getThreadAllocatedMemory0
  (JNIEnv *env, jclass cls, jlong tid)
{
  //实际实现方法是 GetOneThreadAllocatedMemory
  return jmm_interface->GetOneThreadAllocatedMemory(env, tid);
}

management.cpp

JVM_ENTRY(jlong, jmm_GetOneThreadAllocatedMemory(JNIEnv *env, jlong thread_id))
  if (thread_id < 0) {
    THROW_MSG_(vmSymbols::java_lang_IllegalArgumentException(),
               "Invalid thread ID", -1);
  }
  //获取当前线程
  if (thread_id == 0) { // current thread
    //调用 cooked_allocated_bytes,和采集 jdk.ThreadAllocationStatistics 调用的底层方法一样
    return thread->cooked_allocated_bytes();
  }

  //根据线程号获取线程
  ThreadsListHandle tlh;
  JavaThread* java_thread = tlh.list()->find_JavaThread_from_java_tid(thread_id);

  if (java_thread != NULL) {
    //调用 cooked_allocated_bytes,和采集 jdk.ThreadAllocationStatistics 调用的底层方法一样
    return java_thread->cooked_allocated_bytes();
  }
  return -1;
JVM_END

可以看出,其实底层调用的和 JFR 采集 jdk.ThreadAllocationStatistics 事件一样,都是调用 Thread 的 cooked_allocated_bytes() 方法。

针对这个 JFR 事件的一些思考

首先,提出一个观点, jdk.ThreadAllocationStatistics 这个事件并不太消耗性能 。原因有二:

1.统计线程分配大小是近似统计,并不需要进入全局安全点 统计。同时也代表,jdk.ThreadAllocationStatistics 同一时间的不同线程事件的分配大小实际并不是同一时间点的,因为没有进入安全点暂停所有线程 。

2.统计仅仅是原子读取每个线程的分配对象大小这个变量,之后加上每个线程当前 TLAB 分配对象大小(这个大小是通过读取两个指针地址获取的), 可以看出操作是很轻量级的 。即使有 很多线程,也不会增加多少性能负担 。

然后,默认配置的采集周期,并不能满足我们的需求。默认的采集周期是 everyChunk,默认的 chunk 大小(maxchunksize)是 12M,也就是每采集 12M 的 JFR 事件之后,采集一次 jdk.ThreadAllocationStatistics。这是不太可控的,我一般配置为 每过 5s 采集一次 。这样对于我们上面提到的那两个需要这个事件的场景也是很适合的。

E:\Java\jdk-8.391\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true "-Dmanagement.endpoints.jmx.exposure.include=*" "-javaagent:D:\IntelliJ IDEA 2022.3.2\lib\idea_rt.jar=7928:D:\IntelliJ IDEA 2022.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\Java\jdk-8.391\jre\lib\charsets.jar;E:\Java\jdk-8.391\jre\lib\deploy.jar;E:\Java\jdk-8.391\jre\lib\ext\access-bridge-64.jar;E:\Java\jdk-8.391\jre\lib\ext\cldrdata.jar;E:\Java\jdk-8.391\jre\lib\ext\dnsns.jar;E:\Java\jdk-8.391\jre\lib\ext\jaccess.jar;E:\Java\jdk-8.391\jre\lib\ext\jfxrt.jar;E:\Java\jdk-8.391\jre\lib\ext\localedata.jar;E:\Java\jdk-8.391\jre\lib\ext\nashorn.jar;E:\Java\jdk-8.391\jre\lib\ext\sunec.jar;E:\Java\jdk-8.391\jre\lib\ext\sunjce_provider.jar;E:\Java\jdk-8.391\jre\lib\ext\sunmscapi.jar;E:\Java\jdk-8.391\jre\lib\ext\sunpkcs11.jar;E:\Java\jdk-8.391\jre\lib\ext\zipfs.jar;E:\Java\jdk-8.391\jre\lib\javaws.jar;E:\Java\jdk-8.391\jre\lib\jce.jar;E:\Java\jdk-8.391\jre\lib\jfr.jar;E:\Java\jdk-8.391\jre\lib\jfxswt.jar;E:\Java\jdk-8.391\jre\lib\jsse.jar;E:\Java\jdk-8.391\jre\lib\management-agent.jar;E:\Java\jdk-8.391\jre\lib\plugin.jar;E:\Java\jdk-8.391\jre\lib\resources.jar;E:\Java\jdk-8.391\jre\lib\rt.jar;D:\Desktop\2222\target\classes;D:\apache-maven-3.9.2\maven-cangku\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.13.5\jackson-datatype-jsr310-2.13.5.jar;D:\apache-maven-3.9.2\maven-cangku\com\fasterxml\jackson\core\jackson-annotations\2.13.5\jackson-annotations-2.13.5.jar;D:\apache-maven-3.9.2\maven-cangku\com\fasterxml\jackson\core\jackson-core\2.13.5\jackson-core-2.13.5.jar;D:\apache-maven-3.9.2\maven-cangku\com\fasterxml\jackson\core\jackson-databind\2.13.5\jackson-databind-2.13.5.jar;D:\apache-maven-3.9.2\maven-cangku\org\springframework\boot\spring-boot-starter-web\2.7.18\spring-boot-starter-web-2.7.18.jar;D:\apache-maven-3.9.2\maven-cangku\o
最新发布
10-25
<think>我们正在解析一个Java启动命令,该命令用于启动一个Java应用程序。命令如下: E:\Java\jdk-8.391\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true "-Dmanagement.endpoints.jmx.exposure.include=*" "-javaagent:D:\IntelliJ IDEA 2022.3.2\lib\idea_rt.jar=7928:D:\IntelliJ IDEA 2022.3.2\bin" -Dfile.encoding=UTF-8 -classpath ... 我们将逐一解释这些参数的含义,并讨论可能的优化建议和排查问题的方法。 ### 1. 基本命令 - `E:\Java\jdk-8.391\bin\java.exe`: 这是Java可执行文件的路径,表明使用的是JDK 8版本(1.8.0_391)。 ### 2. JVM参数 - `-XX:TieredStopAtLevel=1`: 这个参数用于控制JIT编译器的分层编译。设置成1表示只使用C1编译器(客户端编译器)进行编译,而不使用C2编译器(服务器端编译器)。这可以加快启动速度,但可能会降低长期运行的性能。通常用于开发环境,以缩短启动时间。 - `-noverify`: 禁用字节码验证。这可以加快启动速度,但会降低安全性(因为字节码不会被验证)。同样,通常仅在开发环境中使用。 ### 3. 系统属性(-D参数) 这些参数设置系统属性,供应用程序使用。 - `-Dspring.output.ansi.enabled=always`: 启用Spring Boot的ANSI彩色输出,使控制台输出的日志具有颜色,提高可读性。设置为`always`表示即使在非终端环境下也强制输出颜色。 - `-Dcom.sun.management.jmxremote`: 启用JMX远程管理。这允许通过JMX监控和管理JVM(例如使用JConsole或VisualVM)。 - `-Dspring.jmx.enabled=true`: 启用Spring的JMX支持,使得Spring管理的Bean可以通过JMX进行管理。 - `-Dspring.liveBeansView.mbeanDomain`: 设置Spring的LiveBeansView的MBean域名。这个属性通常需要指定一个域名,但这里没有赋值,可能是遗漏了值?这可能导致问题。通常应该设置为一个字符串,例如`-Dspring.liveBeansView.mbeanDomain=myapp`。如果未设置值,可能不会启用LiveBeansView。 - `-Dspring.application.admin.enabled=true`: 启用Spring应用程序的管理功能,允许通过JMX远程管理应用程序(如启动、停止)。 - `-Dmanagement.endpoints.jmx.exposure.include=*`: 暴露所有的Spring Boot Actuator端点通过JMX。这样,可以通过JMX访问所有监控和管理端点。 - `-Dfile.encoding=UTF-8`: 设置文件编码为UTF-8,确保应用程序正确地读取和写入文件。 ### 4. Java代理 - `"-javaagent:D:\IntelliJ IDEA 2022.3.2\lib\idea_rt.jar=7928:D:\IntelliJ IDEA 2022.3.2\bin"`: 这是IntelliJ IDEA的Java代理,用于在IDE中运行应用程序时提供调试支持。参数`7928`是端口号,后面的路径是IDE的安装目录。这个代理通常用于在IDE中运行应用程序时,支持热部署、调试等功能。 ### 5. 类路径 - `-classpath ...`: 指定了应用程序的类路径,包括所有依赖的JAR包和类文件。实际路径可能很长,这里用省略号表示。 ### 可能的问题排查与优化建议 #### 问题排查 1. **启动失败**:如果应用程序启动失败,可以检查以下方面: - 检查`-Dspring.liveBeansView.mbeanDomain`没有设置值,这可能导致Spring配置问题。考虑为其设置一个值,或者移除该参数(如果不需要LiveBeansView)。 - 检查端口冲突:JMX默认使用一个端口(通常为1099),如果被占用会导致启动失败。另外,IntelliJ的调试代理端口(7928)也可能冲突。 - 版本兼容性:确保所有依赖的库(如Spring Boot、MyBatis等)版本兼容。参考引用[3]中提到的,版本不兼容可能导致启动失败。 2. **性能问题**:使用`-XX:TieredStopAtLevel=1`和`-noverify`可以加快启动,但可能降低运行时性能。如果应用程序需要高性能,建议在生产环境中移除这些参数,并启用完整的JIT编译(即不使用`-XX:TieredStopAtLevel=1`)和字节码验证(不使用`-noverify`)。 3. **编码问题**:确保`-Dfile.encoding=UTF-8`设置正确,避免中文乱码等问题。 4. **JMX连接问题**:如果需要远程JMX监控,可能需要配置额外的参数(例如端口、认证等)。当前配置只启用了本地JMX,远程连接可能需要更多设置。 #### 优化建议 - **生产环境调整**:在生产环境中,应移除开发相关的参数(如`-XX:TieredStopAtLevel=1`、`-noverify`、IntelliJ的Java代理等)。同时,JMX的远程连接应配置安全措施(如密码认证、SSL等)。 - **Actuator安全**:暴露所有Actuator端点(`management.endpoints.jmx.exposure.include=*`)可能带来安全风险。在生产环境中,应该只暴露必要的端点,例如`health`、`info`等。 - **JVM参数优化**:根据应用程序需求,可以调整堆内存(`-Xms`和`-Xmx`)、垃圾收集器等参数以获得最佳性能。 ### 参考用户之前的引用 用户引用了三个配置或报错信息: - 引用[1]是一个Spring Cloud Config客户端的配置,包含了配置中心的地址、注册中心地址等。这与当前启动命令没有直接关系,但可能属于同一个项目。如果启动命令中的应用程序依赖配置中心,需要确保配置中心服务(如`http://localhost:8089`)已启动。 - 引用[2]是一个Tomcat启动失败的错误,可能与类冲突或版本不兼容有关。在启动命令中,如果类路径中包含冲突的库,可能会导致类似错误。需要检查依赖版本。 - 引用[3]提到版本冲突问题(MyBatis和tk.mybatis版本不兼容),导致启动失败。因此,在启动命令中,如果类路径中的库版本不兼容,同样可能启动失败。需要确保所有依赖版本匹配。 ### 总结 该启动命令配置了快速启动(牺牲部分运行时性能)和JMX监控(包括Spring Boot Actuator端点暴露),适用于开发环境。可能存在的问题包括`-Dspring.liveBeansView.mbeanDomain`未设置值、版本兼容性问题等。优化建议包括移除开发参数、调整JMX安全设置等。 §§相关问题§§ 1. 如何配置远程JMX连接以实现安全监控? 2. 生产环境中如何优化Java启动参数? 3. 如何解决Spring Boot应用程序启动时的类版本冲突问题? 4. 如何设置Spring Boot Actuator端点的安全性? </think>以下是对Java启动命令的解析,涵盖参数含义、优化建议和潜在问题排查: ### 一、命令核心参数解析 1. **Java环境路径** `E:\Java\jdk-8.391\bin\java.exe` → 使用JDK 1.8.0_391版本(注意:官方版本号应为1.8.0_391) 2. **JVM编译优化参数** `-XX:TieredStopAtLevel=1` → 限制JIT编译层级,牺牲运行时性能换取更快启动速度(适用于开发环境) 3. **验证机制控制** `-noverify` → 禁用字节码验证器,可提升启动速度(但降低安全性,生产环境慎用) 4. **Spring相关参数** ```bash -Dspring.output.ansi.enabled=always # 强制启用控制台彩色输出 -Dspring.jmx.enabled=true # 启用Spring JMX监控 -Dspring.liveBeansView.mbeanDomain # 暴露Bean管理接口(值未指定需检查) -Dspring.application.admin.enabled=true # 启用管理接口 "-Dmanagement.endpoints.jmx.exposure.include=*" # 暴露所有Actuator端点 ``` 5. **JMX监控配置** `-Dcom.sun.management.jmxremote` → 启用远程JMX监控(需配合端口/安全配置) 6. **IDE调试支持** `-javaagent:D:\IntelliJ IDEA 2022.3.2\lib\idea_rt.jar=7928:D:\IntelliJ IDEA 2022.3.2\bin` → IntelliJ热部署代理,端口7928(开发环境专用) 7. **编码设置** `-Dfile.encoding=UTF-8` → 强制使用UTF-8编码(解决中文乱码问题) --- ### 二、潜在问题与优化建议 #### ⚠ 风险点排查 1. **版本兼容性问题** - 检查`spring-boot-starter-parent`与JDK 8的兼容性(引用[3]中版本冲突案例)[^3] - 确认依赖库兼容性(如MyBatis版本冲突问题)[^3] 2. **JMX配置缺陷** ```bash -Dcom.sun.management.jmxremote # 缺少以下关键参数: -Dcom.sun.management.jmxremote.port=1099 # 端口未指定 -Dcom.sun.management.jmxremote.ssl=false # 安全设置缺失 -Dcom.sun.management.jmxremote.authenticate=false # 认证未配置 ``` → 可能导致监控连接失败或安全风险(生产环境必须配置SSL和认证) 3. **Actuator端点暴露风险** `-Dmanagement.endpoints.jmx.exposure.include=*` → 生产环境建议改为`health,info`避免敏感信息泄露 4. **参数值缺失** `-Dspring.liveBeansView.mbeanDomain` → 未指定域名值(正确示例:`-Dspring.liveBeansView.mbeanDomain=myapp`) #### ⚙ 优化建议 1. **生产环境调整** ```bash # 移除开发参数 -XX:TieredStopAtLevel=1 → 替换为完整编译模式 -noverify → 移除以启用字节码验证 -javaagent... → 移除IDE调试代理 ``` 2. **内存参数补充** 增加堆内存配置(示例): `-Xms512m -Xmx2048m -XX:MaxMetaspaceSize=256m` 3. **GC优化** JDK 8推荐: `-XX:+UseG1GC -XX:MaxGCPauseMillis=200` --- ### 三、典型问题解决方案 1. **启动失败问题** - 类路径错误:检查`-classpath`路径分隔符(Windows用`;`,Linux用`:`) - 端口冲突:检查`server.port`是否被占用(引用[1]中端口8090)[^1] - 依赖冲突:使用`mvn dependency:tree`排查(参考引用[3]的版本冲突解决法)[^3] 2. **Tomcat启动异常** 如遇`LifecycleException`(引用[2])[^2]: - 检查Servlet API版本兼容性 - 排除冲突的JAR包(如重复的`tomcat-embed-core`) 3. **配置中心连接** 结合引用[1]的Config客户端配置[^1]: ```yaml cloud.config.uri=http://localhost:8089 # 确保配置中心已启动 eureka.client.service-url.defaultZone=... # 检查注册中心连通性 ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值