1. 概述

性能分析能深入揭示应用运行时的行为。Java生态中有多种主流分析工具,如NetBeans Profiler、JProfiler和VisualVM。其中NetBeans Profiler提供了强大的免费功能。

本文将演示如何通过编程方式使用NetBeans Profiler API:创建示例项目、生成堆转储文件,并利用API进行分析。

2. NetBeans Profiler 简介

NetBeans IDE内置免费的分析器,可用于分析Java应用。它通过IDE中的直观界面提供CPU性能和内存使用分析功能。

但NetBeans Profiler也开放了其分析API供编程使用。这对自动化堆转储分析特别有用,可减少对GUI工具的依赖。

堆转储是应用在某个时间点的内存快照。它能有效反映内存使用情况,包含内存中的存活对象、类与字段信息,以及对象间的引用关系

3. 示例项目搭建

要使用NetBeans Profiler API,需在pom.xml中添加依赖:

<dependency>
    <groupId>org.netbeans.modules</groupId>
    <artifactId>org-netbeans-lib-profiler</artifactId>
    <version>RELEASE220</version>
</dependency>

此依赖提供JavaClassesInstances等类,用于分析类、实例数量及内存占用。

接下来创建示例项目并分析其堆转储:

class SolarSystem {

    private static final Logger LOGGER = Logger.getLogger(SolarSystem.class.getName());

    private int id;

    private String name;

    private List<String> planet = new ArrayList<>();

    // 构造方法省略

    public void logSolarSystem() {
        LOGGER.info(name);
        LOGGER.info(String.valueOf(id));
        LOGGER.info(planet.toString());
    }
}

上述代码定义了SolarSystem类,用于记录太阳系的名称、ID和行星信息。

应用运行时,可通过jmap工具获取堆转储文件,也可编程方式生成:

static void dumpHeap(String filePath, boolean live) throws IOException {
    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
    HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
      server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
    mxBean.dumpHeap(filePath, live);
}

此代码通过MBeanServerHotSpotDiagnosticMXBean实现堆转储。

创建SolApp类并实例化SolarSystem

class SolApp {

    static void solarSystem() throws IOException {
        List<String> planet = new ArrayList<>();
        planet.add("Mercury");
        planet.add("Mars");
        planet.add("Earth");
        planet.add("Venus");
        SolarSystem solarSystem = new SolarSystem(1, "Sol System", planet);
        solarSystem.logSolarSystem();

        HeapDump.dumpHeap("solarSystem.hprof", true);
    }
}

执行后,堆转储文件solarSystem.hprof会生成到项目根目录。

通过以下代码加载堆转储文件:

Heap heap = HeapFactory.createHeap(new File("solarSystem.hprof"));

现在可调用Heap对象的各种方法进行深入分析,包括:

  • 检查类的内存占用
  • 检测潜在内存泄漏
  • 基于分析结果优化代码性能

4. 堆转储概览

首先获取堆转储的总体信息:

static void heapDumpSummary() {
    HeapSummary summary = heap.getSummary();
    LOGGER.info("Total instances: " + summary.getTotalLiveInstances());
    LOGGER.info("Total bytes: " + summary.getTotalLiveBytes());
    LOGGER.info("Time: " + summary.getTime());
    LOGGER.info("GC Roots: " + heap.getGCRoots().size());
    LOGGER.info("Total classes: " + heap.getAllClasses().size());
}

控制台输出如下:

INFO com.baeldung.netbeanprofiler.SolApp -- Total instances: 79893
INFO com.baeldung.netbeanprofiler.SolApp -- Total bytes: 6235526
INFO com.baeldung.netbeanprofiler.SolApp -- Time: 1724568603079
INFO com.baeldung.netbeanprofiler.SolApp -- GC Roots: 2612
INFO com.baeldung.netbeanprofiler.SolApp -- Total classes: 3207

结果显示:

  • 存活对象总数:79,893个
  • 总内存占用:约6MB
  • 类总数:3,207个
  • GC Roots数量:2,612个

这些存活对象由3,207个类的实例组成,占用约6MB内存

5. 类直方图分析

获取堆转储概览后,进一步分析类的实例数量:

List<JavaClass> unmodifiableClasses = heap.getAllClasses();
List<JavaClass> classes = new ArrayList<>(unmodifiableClasses);
classes.sort((c1, c2) -> Long.compare(c2.getInstancesCount(), c1.getInstancesCount()));

按实例数量排序后,输出前5名:

for (int i = 0; i < Math.min(5, classes.size()); i++) {
    JavaClass javaClass = classes.get(i);
    LOGGER.info("  " + javaClass.getName());
    LOGGER.info("    Instances: " + javaClass.getInstancesCount());
    LOGGER.info("    Total size: " + javaClass.getAllInstancesSize() + " bytes");
}

控制台输出:

INFO com.baeldung.netbeanprofiler.SolApp --   byte[]
INFO com.baeldung.netbeanprofiler.SolApp --     Instances: 18996
INFO com.baeldung.netbeanprofiler.SolApp --     Total size: 2714375 bytes
INFO com.baeldung.netbeanprofiler.SolApp --   java.lang.String
INFO com.baeldung.netbeanprofiler.SolApp --     Instances: 18014
INFO com.baeldung.netbeanprofiler.SolApp --     Total size: 540420 bytes
INFO com.baeldung.netbeanprofiler.SolApp --   java.util.concurrent.ConcurrentHashMap$Node
INFO com.baeldung.netbeanprofiler.SolApp --     Instances: 5522
INFO com.baeldung.netbeanprofiler.SolApp --     Total size: 242968 bytes

关键发现:

  • byte[]String类实例最多(18,996和18,014个)
  • 这符合预期,因为SolarSystem类底层依赖String对象

6. 分析SolarSystem对象

6.1 实例数量与内存占用

获取SolarSystem类的统计信息:

static void solarSystemSummary() {
    JavaClass solarSystemClass = heap.getJavaClassByName("com.baeldung.netbeanprofiler.galaxy.SolarSystem");
    List<Instance> instances = solarSystemClass.getInstances();
    long totalSize = 0;
    long instancesNumber = solarSystemClass.getInstancesCount();
    for (Instance instance : instances) {
        totalSize += instance.getSize();
    }
    LOGGER.info("Total SolarSystem instances: " + instancesNumber);
    LOGGER.info("Total memory used by SolarSystem instances: " + totalSize);
}

输出结果:

INFO com.baeldung.netbeanprofiler.SolApp - Total SolarSystem instances: 1
INFO com.baeldung.netbeanprofiler.SolApp - Total memory used by SolarSystem instances: 36

验证结果:

  • 仅创建1个SolarSystem实例
  • 占用36字节内存
  • 与示例代码中的实例化行为一致

6.2 字段详情分析

深入分析类的字段信息:

static void logFieldDetails() {
    JavaClass solarSystemClass = heap.getJavaClassByName("com.baeldung.netbeanprofiler.galaxy.SolarSystem");

    List<Field> fields = solarSystemClass.getFields();
    for (Field field : fields) {
        LOGGER.info("Field: " + field.getName());
        LOGGER.info("Type: " + field.getType().getName());
    }
}

此代码会输出SolarSystem类所有字段的名称和类型。

7. 探索GC Roots

GC Roots能清晰反映垃圾回收器的行为。被GC Roots直接或间接引用的对象不会被回收,表明这些对象仍然存活。

获取所有GC Roots:

Collection<GCRoot> gcRoots = heap.getGCRoots();

分类统计不同类型的GC Roots:

int threadObj = 0, jniGlobal = 0, jniLocal = 0, javaFrame = 0, other = 0;

for (GCRoot gcRoot : gcRoots) {
    Instance instance = gcRoot.getInstance();
    String kind = gcRoot.getKind();

    switch (kind) {
        case THREAD_OBJECT:
            threadObj++;
            break;
        case JNI_GLOBAL:
            jniGlobal++;
            break;
        case JNI_LOCAL:
            jniLocal++;
            break;
        case JAVA_FRAME:
            javaFrame++;
            break;
        default:
            other++;
    }
}

关键类型说明:

  • Thread Objects:堆转储时的活跃线程
  • Java Frames:JVM栈帧中的对象引用
  • JNI Global/Local:Java本地接口的全局/局部引用

输出统计结果:

LOGGER.info("\nGC Root Summary:");
LOGGER.info("  Thread Objects: " + threadObj);
LOGGER.info("  JNI Global References: " + jniGlobal);
LOGGER.info("  JNI Local References: " + jniLocal);
LOGGER.info("  Java Frames: " + javaFrame);
LOGGER.info("  Other: " + other);

控制台输出:

INFO com.baeldung.netbeanprofiler.SolApp --   Thread Objects: 8
INFO com.baeldung.netbeanprofiler.SolApp --   JNI Global References: 122
INFO com.baeldung.netbeanprofiler.SolApp --   JNI Local References: 1
INFO com.baeldung.netbeanprofiler.SolApp --   Java Frames: 481
INFO com.baeldung.netbeanprofiler.SolApp --   Other: 2000

关键数据:

  • 存活线程:8个
  • Java栈帧:481个
  • JNI全局引用:122个

8. 总结

本文通过实际案例演示了NetBeans Profiler API的核心功能:

  • ✅ 编程方式生成堆转储文件
  • ✅ 获取堆转储概览信息
  • ✅ 分析类实例分布
  • ✅ 深入特定类分析
  • ✅ 探索GC Roots结构

这些技术为自动化性能分析提供了坚实基础,特别适合集成到CI/CD流程中。完整示例代码可在GitHub仓库获取。


原始标题:Programmatic Usage of NetBeans Profiler | Baeldung