NetBeans Profiler 的程式設計使用
1. 概述
對應用程式進行分析可以深入了解其運行時的行為。 Java 生態系統中有各種流行的分析器,例如用於通用分析的 NetBeans Profiler、JProfiler 和 VisualVM。 NetBeans Profiler 免費提供強大的功能。
在本教程中,我們將透過建立範例專案、取得堆轉儲並使用 NetBeans Profiler API 進行分析,深入了解如何以程式設計方式使用 NetBeans Profiler API。
2.NetBeans 分析器
NetBeans IDE 提供免費的分析器來分析 Java 應用程式。它提供透過 IDE 中直覺的嵌入式 UI 評估 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>
這個依賴提供了JavaClasses
和Instances
等各種類別來幫助我們分析類別、創建的實例數量以及使用的記憶體。
接下來,讓我們建立一個簡單的專案並分析它的堆轉儲:
class SolarSystem {
private static final Logger LOGGER = Logger.getLogger(SolarSystem.class.getName());
private int id;
private String name;
private List<String> planet = new ArrayList<>();
// constructors
public void logSolarSystem() {
LOGGER.info(name);
LOGGER.info(String.valueOf(id));
LOGGER.info(planet.toString());
}
}
在上面的程式碼中,我們定義了一個名為SolarSystem
的類,並將太陽系中的name
、 id,
和planets
記錄到控制台。
當應用程式運行時,我們可以使用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);
}
在上面的程式碼中,我們建立MBeabServer
和HotSpotDiagnosticMXBean
物件來取得堆轉儲。
接下來,我們建立一個名為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
類,並以程式設計方式取得堆轉儲進行分析。執行後, solarSystem.hprof
檔案將轉儲到專案根目錄中。
此外,讓我們建立一個Heap
物件來載入堆轉儲:
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());
}
在上面的程式碼中,我們建立一個HeapSummary
物件並呼叫它的各種方法來檢查轉儲檔案。
這是日誌輸出:
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
結果顯示,收集堆轉儲時有 79893 個活動物件處於活動狀態。這些活動實例大約佔用 6 MiB,共有 3207 個類,其實例佔 79893 個活動物件。
轉儲時 GC 根數為 2612。
5. 類別直方圖
概述了堆轉儲後,讓我們檢查一下應用程式中使用的類別和實例數量。
首先,讓我們建立一個物件來獲取範例應用程式中的所有類別:
List<JavaClass> unmodifiableClasses = heap.getAllClasses();
接下來,讓我們建立一個List
物件來儲存用於排序的類別:
List<JavaClass> classes = new ArrayList<>(unmodifiableClasses);
classes.sort((c1, c2) -> Long.compare(c2.getInstancesCount(), c1.getInstancesCount()));
然後,讓我們循環遍歷JavaClass
物件:
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");
}
在上面的程式碼中,我們呼叫javaClass
的getName()
、 getInstancesCount(),
和getAllInstancesSize()
方法分別取得類別名稱、建立的實例總數和實例的總大小。
這是控制台輸出:
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
類別的實例數量最多,分別為 18996 和 18014 個實例。這是預期的,因為我們的SolarSystem
類別在底層依賴String
物件。
6. 分析SolarSystem
對象
此外,讓我們檢查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);
}
在這裡,我們建立一個JavaClass
物件並在堆疊實例上呼叫getJavaClassByName()
方法。接下來,我們取得類別的實例並將實例的數量及其大小記錄到控制台:
INFO com.baeldung.netbeanprofiler.SolApp - Total SolarSystem instances: 1
INFO com.baeldung.netbeanprofiler.SolApp - Total memory used by SolarSystem instances: 36
從控制台輸出來看, SolarSystem
實例是使用 36 位元組建立的。這與範例程式碼中建立的實例數量相匹配,表明SolarSystem
物件已按預期實例化。
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
類別並取得其所有實例。然後我們創建一個集合Field
物件並迭代該集合。最後,我們將欄位的名稱和類型記錄到控制台。
7. 探索 GC 根
GC 根提供了垃圾收集器行為的清晰描述。 GC 根直接或間接引用的任何物件都不會被垃圾回收。它表明該對象仍然存在並且尚未準備好進行清潔。
讓我們在堆物件上呼叫getGCRoots()
方法來收集所有 GC 根:
Collection<GCRoot> gcRoots = heap.getGCRoots();
在上面的程式碼中,我們在heap
實例上呼叫getGCRoots()
方法來取得 GC 根的集合。
接下來,我們建立一個變數來儲存不同 GC 根的計數:
int threadObj = 0, jniGlobal = 0, jniLocal = 0, javaFrame = 0, other = 0;
然後,讓我們循環遍歷 GC 根併計算 Thread 物件、Java Native Interface (JNI) Global 和 Local 物件以及 Java Frame 的數量:
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++;
}
}
在這裡,我們迭代 GC 根併計算 Thread 物件、Java 框架、JNI 全域引用、JNI 本地引用等的數量。
線程物件是進行堆轉儲時的活動線程。它引用的物件被認為是活動的,不會被垃圾收集。 Java 訊框表示 JVM 堆疊訊框中的物件參考。它保存局部變數和方法呼叫詳細資訊。
此外,JNI 允許 Java 程式碼與本機程式碼(例如 C 或 C++)互動。本機引用在建立它們的本機方法的範圍內有效,而全域參考則保留對超出單一本機方法呼叫範圍的 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 個 Thread 物件仍然存活,Java Frame 計數為 481 個。
八、結論
在本文中,我們透過以程式設計方式取得堆轉儲並進一步分析它,了解了 NetBeans Profiler API 的基本分析功能。此外,我們也了解如何取得堆摘要並挑選出一個類別進行分析。
與往常一樣,範例的原始程式碼可在 GitHub 上取得。