概述
VisualVM提供了强大的插件架构,允许开发者扩展其功能以满足特定的监控和分析需求。本章将详细介绍如何开发、部署和管理VisualVM插件。
VisualVM插件架构
插件系统概述
VisualVM基于NetBeans平台构建,采用模块化架构:
- 模块系统:基于OSGi规范的模块化框架
- API层:提供丰富的扩展点和服务接口
- 插件管理:支持插件的动态加载、卸载和更新
- 服务发现:自动发现和注册插件服务
核心组件
1. 应用程序类型(Application Types)
// CustomApplicationType.java
import org.graalvm.visualvm.application.Application;
import org.graalvm.visualvm.application.type.ApplicationType;
import org.graalvm.visualvm.application.type.ApplicationTypeFactory;
import org.graalvm.visualvm.application.jvm.Jvm;
public class CustomApplicationType extends ApplicationType {
private static final String TYPE_NAME = "Custom Application";
private static final String TYPE_VERSION = "1.0";
private static final String TYPE_DESCRIPTION = "自定义应用程序类型";
public CustomApplicationType(Application application) {
super(application, TYPE_NAME, TYPE_VERSION, TYPE_DESCRIPTION);
}
@Override
public String getName() {
return TYPE_NAME;
}
@Override
public String getVersion() {
return TYPE_VERSION;
}
@Override
public String getDescription() {
return TYPE_DESCRIPTION;
}
// 工厂类
public static class Factory extends ApplicationTypeFactory {
@Override
public ApplicationType createApplicationType(Application application) {
// 检查应用程序是否符合自定义类型的条件
if (isCustomApplication(application)) {
return new CustomApplicationType(application);
}
return null;
}
private boolean isCustomApplication(Application application) {
Jvm jvm = Jvm.getJVMFor(application);
if (jvm != null && jvm.isGetSystemPropertiesSupported()) {
String mainClass = jvm.getMainClass();
// 根据主类名判断是否为自定义应用
return mainClass != null && mainClass.contains("CustomApp");
}
return false;
}
}
}
2. 数据源(Data Sources)
// CustomDataSource.java
import org.graalvm.visualvm.core.datasource.DataSource;
import org.graalvm.visualvm.core.datasource.descriptor.DataSourceDescriptor;
import org.graalvm.visualvm.core.datasource.descriptor.DataSourceDescriptorFactory;
import org.graalvm.visualvm.application.Application;
public class CustomDataSource extends DataSource {
private final Application application;
private final String customData;
public CustomDataSource(Application application, String customData) {
super(application);
this.application = application;
this.customData = customData;
}
public Application getApplication() {
return application;
}
public String getCustomData() {
return customData;
}
// 描述符类
public static class Descriptor extends DataSourceDescriptor<CustomDataSource> {
private static final String IMAGE_PATH = "org/example/resources/custom_icon.png";
public Descriptor(CustomDataSource dataSource) {
super(dataSource, "Custom Data", "自定义数据源", null, 0);
}
@Override
public String getName() {
return "Custom: " + getDataSource().getCustomData();
}
@Override
public String getDescription() {
return "自定义数据源: " + getDataSource().getCustomData();
}
}
// 描述符工厂
public static class DescriptorFactory extends DataSourceDescriptorFactory<CustomDataSource> {
@Override
public DataSourceDescriptor<CustomDataSource> createDataSourceDescriptor(CustomDataSource dataSource) {
return new Descriptor(dataSource);
}
}
}
3. 视图(Views)
// CustomView.java
import org.graalvm.visualvm.core.ui.DataSourceView;
import org.graalvm.visualvm.core.ui.components.DataViewComponent;
import org.graalvm.visualvm.application.Application;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class CustomView extends DataSourceView {
private final Application application;
private DataViewComponent dataViewComponent;
private JLabel statusLabel;
private JButton refreshButton;
public CustomView(Application application) {
super(application, "Custom View", null, 0, false);
this.application = application;
}
@Override
protected DataViewComponent createComponent() {
// 创建主面板
JPanel mainPanel = new JPanel(new BorderLayout());
// 创建工具栏
JPanel toolbarPanel = createToolbar();
mainPanel.add(toolbarPanel, BorderLayout.NORTH);
// 创建内容面板
JPanel contentPanel = createContentPanel();
mainPanel.add(contentPanel, BorderLayout.CENTER);
// 创建状态面板
JPanel statusPanel = createStatusPanel();
mainPanel.add(statusPanel, BorderLayout.SOUTH);
// 创建DataViewComponent
DataViewComponent.MasterView masterView = new DataViewComponent.MasterView(
"Custom Application Monitor", null, mainPanel);
DataViewComponent.MasterViewConfiguration masterConfig =
new DataViewComponent.MasterViewConfiguration(false);
dataViewComponent = new DataViewComponent(masterView, masterConfig);
// 添加详细视图
addDetailViews();
return dataViewComponent;
}
private JPanel createToolbar() {
JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT));
refreshButton = new JButton("刷新");
refreshButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
refreshData();
}
});
JButton configButton = new JButton("配置");
configButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showConfigDialog();
}
});
toolbar.add(refreshButton);
toolbar.add(configButton);
return toolbar;
}
private JPanel createContentPanel() {
JPanel content = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// 应用信息
gbc.gridx = 0; gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(5, 5, 5, 5);
content.add(new JLabel("应用程序:"), gbc);
gbc.gridx = 1;
content.add(new JLabel(application.getId()), gbc);
// 自定义监控数据
gbc.gridx = 0; gbc.gridy = 1;
content.add(new JLabel("监控状态:"), gbc);
statusLabel = new JLabel("正在加载...");
gbc.gridx = 1;
content.add(statusLabel, gbc);
return content;
}
private JPanel createStatusPanel() {
JPanel status = new JPanel(new FlowLayout(FlowLayout.LEFT));
status.add(new JLabel("就绪"));
return status;
}
private void addDetailViews() {
// 添加性能详细视图
JPanel performancePanel = createPerformancePanel();
DataViewComponent.DetailsView performanceView = new DataViewComponent.DetailsView(
"性能监控", null, 10, performancePanel, null);
dataViewComponent.addDetailsView(performanceView, DataViewComponent.TOP_LEFT);
// 添加配置详细视图
JPanel configPanel = createConfigPanel();
DataViewComponent.DetailsView configView = new DataViewComponent.DetailsView(
"配置信息", null, 20, configPanel, null);
dataViewComponent.addDetailsView(configView, DataViewComponent.TOP_RIGHT);
}
private JPanel createPerformancePanel() {
JPanel panel = new JPanel(new BorderLayout());
// 创建性能图表
JPanel chartPanel = new JPanel();
chartPanel.setPreferredSize(new Dimension(300, 200));
chartPanel.setBorder(BorderFactory.createTitledBorder("性能趋势"));
// 这里可以集成JFreeChart或其他图表库
JLabel chartPlaceholder = new JLabel("性能图表", JLabel.CENTER);
chartPanel.add(chartPlaceholder);
panel.add(chartPanel, BorderLayout.CENTER);
return panel;
}
private JPanel createConfigPanel() {
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0; gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(2, 2, 2, 2);
panel.add(new JLabel("监控间隔:"), gbc);
gbc.gridx = 1;
panel.add(new JLabel("5秒"), gbc);
gbc.gridx = 0; gbc.gridy = 1;
panel.add(new JLabel("数据保留:"), gbc);
gbc.gridx = 1;
panel.add(new JLabel("1小时"), gbc);
return panel;
}
private void refreshData() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
statusLabel.setText("数据已刷新 - " + new java.util.Date());
}
});
}
private void showConfigDialog() {
JOptionPane.showMessageDialog(dataViewComponent.getView(),
"配置对话框\n这里可以添加自定义配置选项",
"配置", JOptionPane.INFORMATION_MESSAGE);
}
}
4. 视图提供者(View Providers)
// CustomViewProvider.java
import org.graalvm.visualvm.core.ui.DataSourceViewProvider;
import org.graalvm.visualvm.core.ui.DataSourceView;
import org.graalvm.visualvm.application.Application;
import org.graalvm.visualvm.application.type.ApplicationType;
public class CustomViewProvider extends DataSourceViewProvider<Application> {
private static final int VIEW_POSITION = 100;
@Override
public boolean supportsViewFor(Application application) {
// 检查是否支持该应用程序
ApplicationType appType = ApplicationType.getApplicationTypeFor(application);
return appType instanceof CustomApplicationType;
}
@Override
public DataSourceView createView(Application application) {
return new CustomView(application);
}
@Override
public int getViewPosition(Application application) {
return VIEW_POSITION;
}
}
插件开发环境搭建
开发工具准备
- NetBeans IDE:推荐使用NetBeans进行插件开发
- VisualVM源码:从GitHub获取VisualVM源码
- Maven:用于构建和依赖管理
- JDK:Java 8或更高版本
项目结构
custom-visualvm-plugin/
├── pom.xml
├── src/
│ └── main/
│ ├── java/
│ │ └── org/
│ │ └── example/
│ │ └── visualvm/
│ │ ├── CustomApplicationType.java
│ │ ├── CustomDataSource.java
│ │ ├── CustomView.java
│ │ └── CustomViewProvider.java
│ └── resources/
│ ├── META-INF/
│ │ └── services/
│ │ ├── org.graalvm.visualvm.application.type.ApplicationTypeFactory
│ │ ├── org.graalvm.visualvm.core.datasource.descriptor.DataSourceDescriptorFactory
│ │ └── org.graalvm.visualvm.core.ui.DataSourceViewProvider
│ └── org/
│ └── example/
│ └── resources/
│ └── custom_icon.png
└── nbproject/
└── project.xml
Maven配置
<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>custom-visualvm-plugin</artifactId>
<version>1.0.0</version>
<packaging>nbm</packaging>
<name>Custom VisualVM Plugin</name>
<description>自定义VisualVM插件示例</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<netbeans.version>RELEASE120</netbeans.version>
<visualvm.version>2.1.4</visualvm.version>
</properties>
<repositories>
<repository>
<id>netbeans</id>
<name>NetBeans Repository</name>
<url>https://netbeans.osuosl.org/content/repositories/releases/</url>
</repository>
</repositories>
<dependencies>
<!-- VisualVM Core -->
<dependency>
<groupId>org.graalvm.visualvm</groupId>
<artifactId>visualvm-core</artifactId>
<version>${visualvm.version}</version>
<scope>provided</scope>
</dependency>
<!-- VisualVM Application -->
<dependency>
<groupId>org.graalvm.visualvm</groupId>
<artifactId>visualvm-application</artifactId>
<version>${visualvm.version}</version>
<scope>provided</scope>
</dependency>
<!-- NetBeans Platform -->
<dependency>
<groupId>org.netbeans.api</groupId>
<artifactId>org-netbeans-api-annotations-common</artifactId>
<version>${netbeans.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.netbeans.api</groupId>
<artifactId>org-openide-util</artifactId>
<version>${netbeans.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.netbeans.api</groupId>
<artifactId>org-openide-util-lookup</artifactId>
<version>${netbeans.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- NetBeans Module Plugin -->
<plugin>
<groupId>org.apache.netbeans.utilities</groupId>
<artifactId>nbm-maven-plugin</artifactId>
<version>4.5</version>
<extensions>true</extensions>
<configuration>
<moduleType>normal</moduleType>
<codeNameBase>org.example.visualvm.custom</codeNameBase>
<publicPackages>
<publicPackage>org.example.visualvm.*</publicPackage>
</publicPackages>
</configuration>
</plugin>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
服务注册
在src/main/resources/META-INF/services/
目录下创建服务注册文件:
# org.graalvm.visualvm.application.type.ApplicationTypeFactory
org.example.visualvm.CustomApplicationType$Factory
# org.graalvm.visualvm.core.datasource.descriptor.DataSourceDescriptorFactory
org.example.visualvm.CustomDataSource$DescriptorFactory
# org.graalvm.visualvm.core.ui.DataSourceViewProvider
org.example.visualvm.CustomViewProvider
高级插件功能
1. 自定义监控数据收集
// CustomDataCollector.java
import org.graalvm.visualvm.application.Application;
import org.graalvm.visualvm.application.jvm.Jvm;
import org.graalvm.visualvm.application.jvm.JvmFactory;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
public class CustomDataCollector {
private final Application application;
private final ScheduledExecutorService scheduler;
private final List<DataCollectionListener> listeners;
private volatile boolean collecting = false;
public CustomDataCollector(Application application) {
this.application = application;
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.listeners = new CopyOnWriteArrayList<>();
}
public void startCollection() {
if (!collecting) {
collecting = true;
scheduler.scheduleAtFixedRate(this::collectData, 0, 5, TimeUnit.SECONDS);
}
}
public void stopCollection() {
collecting = false;
scheduler.shutdown();
}
public void addListener(DataCollectionListener listener) {
listeners.add(listener);
}
public void removeListener(DataCollectionListener listener) {
listeners.remove(listener);
}
private void collectData() {
try {
CustomMetrics metrics = gatherMetrics();
notifyListeners(metrics);
} catch (Exception e) {
// 处理收集错误
System.err.println("数据收集失败: " + e.getMessage());
}
}
private CustomMetrics gatherMetrics() {
Jvm jvm = JvmFactory.getJVMFor(application);
CustomMetrics metrics = new CustomMetrics();
metrics.timestamp = System.currentTimeMillis();
if (jvm != null) {
// 收集JVM指标
if (jvm.isMemoryMonitoringSupported()) {
metrics.heapUsed = jvm.getHeapMemoryUsage().getUsed();
metrics.heapMax = jvm.getHeapMemoryUsage().getMax();
}
if (jvm.isThreadMonitoringSupported()) {
metrics.threadCount = jvm.getThreadCount();
}
// 收集自定义指标
metrics.customValue = collectCustomMetric();
}
return metrics;
}
private double collectCustomMetric() {
// 实现自定义指标收集逻辑
// 例如:通过JMX、HTTP API等方式收集应用特定的指标
return Math.random() * 100; // 示例数据
}
private void notifyListeners(CustomMetrics metrics) {
for (DataCollectionListener listener : listeners) {
try {
listener.onDataCollected(metrics);
} catch (Exception e) {
System.err.println("通知监听器失败: " + e.getMessage());
}
}
}
// 自定义指标数据类
public static class CustomMetrics {
public long timestamp;
public long heapUsed;
public long heapMax;
public int threadCount;
public double customValue;
@Override
public String toString() {
return String.format("CustomMetrics{timestamp=%d, heapUsed=%d, heapMax=%d, threadCount=%d, customValue=%.2f}",
timestamp, heapUsed, heapMax, threadCount, customValue);
}
}
// 数据收集监听器接口
public interface DataCollectionListener {
void onDataCollected(CustomMetrics metrics);
}
}
2. 图表集成
// CustomChartPanel.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
public class CustomChartPanel extends JPanel {
private final List<DataPoint> dataPoints;
private final int maxDataPoints;
private Color lineColor = Color.BLUE;
private Color backgroundColor = Color.WHITE;
private Color gridColor = Color.LIGHT_GRAY;
public CustomChartPanel() {
this(100); // 默认保留100个数据点
}
public CustomChartPanel(int maxDataPoints) {
this.maxDataPoints = maxDataPoints;
this.dataPoints = new CopyOnWriteArrayList<>();
setPreferredSize(new Dimension(400, 200));
setBackground(backgroundColor);
// 添加右键菜单
setupContextMenu();
}
public void addDataPoint(long timestamp, double value) {
synchronized (dataPoints) {
dataPoints.add(new DataPoint(timestamp, value));
// 保持数据点数量在限制内
while (dataPoints.size() > maxDataPoints) {
dataPoints.remove(0);
}
}
SwingUtilities.invokeLater(this::repaint);
}
public void clearData() {
synchronized (dataPoints) {
dataPoints.clear();
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int width = getWidth();
int height = getHeight();
int margin = 20;
// 绘制背景
g2d.setColor(backgroundColor);
g2d.fillRect(0, 0, width, height);
// 绘制网格
drawGrid(g2d, width, height, margin);
// 绘制数据线
drawDataLine(g2d, width, height, margin);
// 绘制坐标轴
drawAxes(g2d, width, height, margin);
g2d.dispose();
}
private void drawGrid(Graphics2D g2d, int width, int height, int margin) {
g2d.setColor(gridColor);
g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{2, 2}, 0));
// 垂直网格线
for (int i = 0; i <= 10; i++) {
int x = margin + (width - 2 * margin) * i / 10;
g2d.drawLine(x, margin, x, height - margin);
}
// 水平网格线
for (int i = 0; i <= 10; i++) {
int y = margin + (height - 2 * margin) * i / 10;
g2d.drawLine(margin, y, width - margin, y);
}
}
private void drawDataLine(Graphics2D g2d, int width, int height, int margin) {
synchronized (dataPoints) {
if (dataPoints.size() < 2) return;
g2d.setColor(lineColor);
g2d.setStroke(new BasicStroke(2));
// 计算数据范围
double minValue = dataPoints.stream().mapToDouble(p -> p.value).min().orElse(0);
double maxValue = dataPoints.stream().mapToDouble(p -> p.value).max().orElse(100);
double valueRange = maxValue - minValue;
if (valueRange == 0) valueRange = 1;
long minTime = dataPoints.get(0).timestamp;
long maxTime = dataPoints.get(dataPoints.size() - 1).timestamp;
long timeRange = maxTime - minTime;
if (timeRange == 0) timeRange = 1;
// 绘制数据线
for (int i = 1; i < dataPoints.size(); i++) {
DataPoint prev = dataPoints.get(i - 1);
DataPoint curr = dataPoints.get(i);
int x1 = margin + (int) ((prev.timestamp - minTime) * (width - 2 * margin) / timeRange);
int y1 = height - margin - (int) ((prev.value - minValue) * (height - 2 * margin) / valueRange);
int x2 = margin + (int) ((curr.timestamp - minTime) * (width - 2 * margin) / timeRange);
int y2 = height - margin - (int) ((curr.value - minValue) * (height - 2 * margin) / valueRange);
g2d.drawLine(x1, y1, x2, y2);
}
}
}
private void drawAxes(Graphics2D g2d, int width, int height, int margin) {
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(1));
// X轴
g2d.drawLine(margin, height - margin, width - margin, height - margin);
// Y轴
g2d.drawLine(margin, margin, margin, height - margin);
// 添加标签
g2d.setFont(new Font("SansSerif", Font.PLAIN, 10));
synchronized (dataPoints) {
if (!dataPoints.isEmpty()) {
double minValue = dataPoints.stream().mapToDouble(p -> p.value).min().orElse(0);
double maxValue = dataPoints.stream().mapToDouble(p -> p.value).max().orElse(100);
// Y轴标签
g2d.drawString(String.format("%.1f", maxValue), 5, margin + 5);
g2d.drawString(String.format("%.1f", minValue), 5, height - margin - 5);
}
}
}
private void setupContextMenu() {
JPopupMenu contextMenu = new JPopupMenu();
JMenuItem clearItem = new JMenuItem("清除数据");
clearItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
clearData();
}
});
JMenuItem colorItem = new JMenuItem("更改颜色");
colorItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Color newColor = JColorChooser.showDialog(CustomChartPanel.this, "选择线条颜色", lineColor);
if (newColor != null) {
lineColor = newColor;
repaint();
}
}
});
contextMenu.add(clearItem);
contextMenu.add(colorItem);
setComponentPopupMenu(contextMenu);
}
// 数据点类
private static class DataPoint {
final long timestamp;
final double value;
DataPoint(long timestamp, double value) {
this.timestamp = timestamp;
this.value = value;
}
}
}
3. 配置管理
// PluginConfiguration.java
import java.util.prefs.Preferences;
import java.awt.Color;
public class PluginConfiguration {
private static final String PREF_NODE = "org/example/visualvm/custom";
private static final Preferences prefs = Preferences.userRoot().node(PREF_NODE);
// 配置键
private static final String KEY_MONITORING_INTERVAL = "monitoring.interval";
private static final String KEY_DATA_RETENTION = "data.retention";
private static final String KEY_CHART_COLOR = "chart.color";
private static final String KEY_AUTO_REFRESH = "auto.refresh";
private static final String KEY_ALERT_THRESHOLD = "alert.threshold";
// 默认值
private static final int DEFAULT_MONITORING_INTERVAL = 5; // 秒
private static final int DEFAULT_DATA_RETENTION = 3600; // 秒
private static final int DEFAULT_CHART_COLOR = Color.BLUE.getRGB();
private static final boolean DEFAULT_AUTO_REFRESH = true;
private static final double DEFAULT_ALERT_THRESHOLD = 80.0;
// 获取配置值
public static int getMonitoringInterval() {
return prefs.getInt(KEY_MONITORING_INTERVAL, DEFAULT_MONITORING_INTERVAL);
}
public static void setMonitoringInterval(int interval) {
prefs.putInt(KEY_MONITORING_INTERVAL, interval);
}
public static int getDataRetention() {
return prefs.getInt(KEY_DATA_RETENTION, DEFAULT_DATA_RETENTION);
}
public static void setDataRetention(int retention) {
prefs.putInt(KEY_DATA_RETENTION, retention);
}
public static Color getChartColor() {
int rgb = prefs.getInt(KEY_CHART_COLOR, DEFAULT_CHART_COLOR);
return new Color(rgb);
}
public static void setChartColor(Color color) {
prefs.putInt(KEY_CHART_COLOR, color.getRGB());
}
public static boolean isAutoRefreshEnabled() {
return prefs.getBoolean(KEY_AUTO_REFRESH, DEFAULT_AUTO_REFRESH);
}
public static void setAutoRefreshEnabled(boolean enabled) {
prefs.putBoolean(KEY_AUTO_REFRESH, enabled);
}
public static double getAlertThreshold() {
return prefs.getDouble(KEY_ALERT_THRESHOLD, DEFAULT_ALERT_THRESHOLD);
}
public static void setAlertThreshold(double threshold) {
prefs.putDouble(KEY_ALERT_THRESHOLD, threshold);
}
// 重置所有配置
public static void resetToDefaults() {
try {
prefs.clear();
} catch (Exception e) {
System.err.println("重置配置失败: " + e.getMessage());
}
}
// 导出配置
public static String exportConfiguration() {
StringBuilder sb = new StringBuilder();
sb.append("# Custom VisualVM Plugin Configuration\n");
sb.append("monitoring.interval=").append(getMonitoringInterval()).append("\n");
sb.append("data.retention=").append(getDataRetention()).append("\n");
sb.append("chart.color=").append(getChartColor().getRGB()).append("\n");
sb.append("auto.refresh=").append(isAutoRefreshEnabled()).append("\n");
sb.append("alert.threshold=").append(getAlertThreshold()).append("\n");
return sb.toString();
}
}
插件构建与部署
构建插件
# 编译插件
mvn clean compile
# 构建NBM文件
mvn package
# 生成的插件文件位于 target/ 目录
# custom-visualvm-plugin-1.0.0.nbm
安装插件
通过VisualVM界面安装:
- 启动VisualVM
- 选择 “工具” -> “插件”
- 点击 “已下载” 标签页
- 点击 “添加插件” 按钮
- 选择生成的.nbm文件
- 按照向导完成安装
手动安装:
# 将NBM文件复制到VisualVM插件目录 cp target/custom-visualvm-plugin-1.0.0.nbm $VISUALVM_HOME/etc/
插件分发
1. 创建更新中心
<!-- update-center.xml --> <?xml version="1.0" encoding="UTF-8"?> <module_updates timestamp="1234567890"> <module codenamebase="org.example.visualvm.custom" distribution="custom-visualvm-plugin-1.0.0.nbm" downloadsize="123456" homepage="https://example.org/plugin" license="Apache-2.0" moduleauthor="Example Developer" needsrestart="true" releasedate="2023/12/01"> <manifest OpenIDE-Module="org.example.visualvm.custom" OpenIDE-Module-Display-Category="Monitoring" OpenIDE-Module-Implementation-Version="1.0.0" OpenIDE-Module-Long-Description="自定义VisualVM监控插件,提供应用程序特定的监控功能。" OpenIDE-Module-Name="Custom VisualVM Plugin" OpenIDE-Module-Short-Description="自定义监控插件" OpenIDE-Module-Specification-Version="1.0"/> </module> </module_updates>
2. 发布到GitHub Releases
# .github/workflows/release.yml
name: Release Plugin
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 8
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
- name: Build plugin
run: mvn clean package
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Upload NBM
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./target/custom-visualvm-plugin-1.0.0.nbm
asset_name: custom-visualvm-plugin-1.0.0.nbm
asset_content_type: application/octet-stream
实践练习
练习1:创建简单监控插件
创建一个监控JVM线程数的简单插件:
// ThreadMonitorPlugin.java
import org.graalvm.visualvm.core.ui.DataSourceView;
import org.graalvm.visualvm.core.ui.components.DataViewComponent;
import org.graalvm.visualvm.application.Application;
import org.graalvm.visualvm.application.jvm.Jvm;
import org.graalvm.visualvm.application.jvm.JvmFactory;
import javax.swing.*;
import java.awt.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadMonitorPlugin extends DataSourceView {
private final Application application;
private JLabel threadCountLabel;
private JLabel peakThreadCountLabel;
private CustomChartPanel chartPanel;
private ScheduledExecutorService scheduler;
public ThreadMonitorPlugin(Application application) {
super(application, "Thread Monitor", null, 0, false);
this.application = application;
}
@Override
protected DataViewComponent createComponent() {
JPanel mainPanel = new JPanel(new BorderLayout());
// 创建信息面板
JPanel infoPanel = createInfoPanel();
mainPanel.add(infoPanel, BorderLayout.NORTH);
// 创建图表面板
chartPanel = new CustomChartPanel();
chartPanel.setBorder(BorderFactory.createTitledBorder("线程数趋势"));
mainPanel.add(chartPanel, BorderLayout.CENTER);
// 启动数据收集
startDataCollection();
DataViewComponent.MasterView masterView = new DataViewComponent.MasterView(
"Thread Monitor", null, mainPanel);
DataViewComponent.MasterViewConfiguration masterConfig =
new DataViewComponent.MasterViewConfiguration(false);
return new DataViewComponent(masterView, masterConfig);
}
private JPanel createInfoPanel() {
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0; gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(5, 5, 5, 5);
panel.add(new JLabel("当前线程数:"), gbc);
gbc.gridx = 1;
threadCountLabel = new JLabel("--");
panel.add(threadCountLabel, gbc);
gbc.gridx = 0; gbc.gridy = 1;
panel.add(new JLabel("峰值线程数:"), gbc);
gbc.gridx = 1;
peakThreadCountLabel = new JLabel("--");
panel.add(peakThreadCountLabel, gbc);
return panel;
}
private void startDataCollection() {
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(this::updateData, 0, 2, TimeUnit.SECONDS);
}
private void updateData() {
Jvm jvm = JvmFactory.getJVMFor(application);
if (jvm != null && jvm.isThreadMonitoringSupported()) {
final int threadCount = jvm.getThreadCount();
final int peakThreadCount = jvm.getPeakThreadCount();
SwingUtilities.invokeLater(() -> {
threadCountLabel.setText(String.valueOf(threadCount));
peakThreadCountLabel.setText(String.valueOf(peakThreadCount));
chartPanel.addDataPoint(System.currentTimeMillis(), threadCount);
});
}
}
@Override
protected void willBeRemoved() {
if (scheduler != null) {
scheduler.shutdown();
}
super.willBeRemoved();
}
}
练习2:集成外部监控API
创建一个集成外部监控API的插件:
// ExternalAPIMonitor.java
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
public class ExternalAPIMonitor {
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
private final String apiEndpoint;
public ExternalAPIMonitor(String apiEndpoint) {
this.apiEndpoint = apiEndpoint;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
this.objectMapper = new ObjectMapper();
}
public CompletableFuture<MetricsData> fetchMetrics() {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiEndpoint + "/metrics"))
.timeout(Duration.ofSeconds(5))
.header("Accept", "application/json")
.GET()
.build();
return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(this::parseResponse)
.exceptionally(this::handleError);
}
private MetricsData parseResponse(HttpResponse<String> response) {
try {
if (response.statusCode() == 200) {
JsonNode json = objectMapper.readTree(response.body());
return new MetricsData(
json.get("cpu_usage").asDouble(),
json.get("memory_usage").asLong(),
json.get("request_count").asInt(),
json.get("error_rate").asDouble()
);
} else {
throw new RuntimeException("HTTP " + response.statusCode());
}
} catch (Exception e) {
throw new RuntimeException("解析响应失败", e);
}
}
private MetricsData handleError(Throwable throwable) {
System.err.println("获取指标失败: " + throwable.getMessage());
return new MetricsData(0, 0, 0, 0); // 返回默认值
}
public static class MetricsData {
public final double cpuUsage;
public final long memoryUsage;
public final int requestCount;
public final double errorRate;
public MetricsData(double cpuUsage, long memoryUsage, int requestCount, double errorRate) {
this.cpuUsage = cpuUsage;
this.memoryUsage = memoryUsage;
this.requestCount = requestCount;
this.errorRate = errorRate;
}
}
}
练习3:创建自定义报告生成器
开发一个能够生成性能报告的插件:
// PerformanceReportGenerator.java
import org.graalvm.visualvm.application.Application;
import org.graalvm.visualvm.application.jvm.Jvm;
import org.graalvm.visualvm.application.jvm.JvmFactory;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class PerformanceReportGenerator {
private final Application application;
private final Map<Long, PerformanceSnapshot> snapshots;
private final ScheduledExecutorService scheduler;
private volatile boolean collecting = false;
public PerformanceReportGenerator(Application application) {
this.application = application;
this.snapshots = new ConcurrentHashMap<>();
this.scheduler = Executors.newSingleThreadScheduledExecutor();
}
public void startCollection() {
if (!collecting) {
collecting = true;
scheduler.scheduleAtFixedRate(this::collectSnapshot, 0, 30, TimeUnit.SECONDS);
}
}
public void stopCollection() {
collecting = false;
scheduler.shutdown();
}
private void collectSnapshot() {
try {
Jvm jvm = JvmFactory.getJVMFor(application);
if (jvm != null) {
PerformanceSnapshot snapshot = new PerformanceSnapshot();
snapshot.timestamp = System.currentTimeMillis();
// 收集内存信息
if (jvm.isMemoryMonitoringSupported()) {
snapshot.heapUsed = jvm.getHeapMemoryUsage().getUsed();
snapshot.heapMax = jvm.getHeapMemoryUsage().getMax();
snapshot.nonHeapUsed = jvm.getNonHeapMemoryUsage().getUsed();
}
// 收集线程信息
if (jvm.isThreadMonitoringSupported()) {
snapshot.threadCount = jvm.getThreadCount();
snapshot.peakThreadCount = jvm.getPeakThreadCount();
}
// 收集类加载信息
if (jvm.isClassMonitoringSupported()) {
snapshot.loadedClassCount = jvm.getLoadedClassCount();
snapshot.totalLoadedClassCount = jvm.getTotalLoadedClassCount();
snapshot.unloadedClassCount = jvm.getUnloadedClassCount();
}
snapshots.put(snapshot.timestamp, snapshot);
// 保持最近1小时的数据
long cutoff = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1);
snapshots.entrySet().removeIf(entry -> entry.getKey() < cutoff);
}
} catch (Exception e) {
System.err.println("收集性能快照失败: " + e.getMessage());
}
}
public String generateReport() {
StringBuilder report = new StringBuilder();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
report.append("# 性能分析报告\n\n");
report.append("**应用程序**: ").append(application.getId()).append("\n");
report.append("**生成时间**: ").append(dateFormat.format(new Date())).append("\n");
report.append("**数据时间范围**: ").append(getDataTimeRange()).append("\n\n");
if (snapshots.isEmpty()) {
report.append("没有可用的性能数据。\n");
return report.toString();
}
// 生成摘要统计
generateSummaryStatistics(report);
// 生成内存分析
generateMemoryAnalysis(report);
// 生成线程分析
generateThreadAnalysis(report);
// 生成类加载分析
generateClassLoadingAnalysis(report);
// 生成趋势分析
generateTrendAnalysis(report);
return report.toString();
}
private String getDataTimeRange() {
if (snapshots.isEmpty()) return "无数据";
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
long minTime = Collections.min(snapshots.keySet());
long maxTime = Collections.max(snapshots.keySet());
return dateFormat.format(new Date(minTime)) + " - " + dateFormat.format(new Date(maxTime));
}
private void generateSummaryStatistics(StringBuilder report) {
report.append("## 摘要统计\n\n");
List<PerformanceSnapshot> snapshotList = new ArrayList<>(snapshots.values());
// 内存统计
OptionalLong maxHeapUsed = snapshotList.stream().mapToLong(s -> s.heapUsed).max();
OptionalDouble avgHeapUsed = snapshotList.stream().mapToLong(s -> s.heapUsed).average();
report.append("**内存使用**:\n");
report.append("- 最大堆内存使用: ").append(formatBytes(maxHeapUsed.orElse(0))).append("\n");
report.append("- 平均堆内存使用: ").append(formatBytes((long)avgHeapUsed.orElse(0))).append("\n");
// 线程统计
OptionalInt maxThreads = snapshotList.stream().mapToInt(s -> s.threadCount).max();
OptionalDouble avgThreads = snapshotList.stream().mapToInt(s -> s.threadCount).average();
report.append("\n**线程统计**:\n");
report.append("- 最大线程数: ").append(maxThreads.orElse(0)).append("\n");
report.append("- 平均线程数: ").append(String.format("%.1f", avgThreads.orElse(0))).append("\n\n");
}
private void generateMemoryAnalysis(StringBuilder report) {
report.append("## 内存分析\n\n");
List<PerformanceSnapshot> snapshotList = new ArrayList<>(snapshots.values());
snapshotList.sort(Comparator.comparing(s -> s.timestamp));
if (snapshotList.size() >= 2) {
PerformanceSnapshot first = snapshotList.get(0);
PerformanceSnapshot last = snapshotList.get(snapshotList.size() - 1);
long heapGrowth = last.heapUsed - first.heapUsed;
long nonHeapGrowth = last.nonHeapUsed - first.nonHeapUsed;
report.append("**内存增长趋势**:\n");
report.append("- 堆内存增长: ").append(formatBytes(heapGrowth));
if (heapGrowth > 0) {
report.append(" ⚠️ 可能存在内存泄漏");
}
report.append("\n");
report.append("- 非堆内存增长: ").append(formatBytes(nonHeapGrowth)).append("\n");
}
// 内存使用率分析
double avgUsageRate = snapshotList.stream()
.filter(s -> s.heapMax > 0)
.mapToDouble(s -> (double)s.heapUsed / s.heapMax * 100)
.average().orElse(0);
report.append("\n**内存使用率**:\n");
report.append("- 平均堆内存使用率: ").append(String.format("%.1f%%", avgUsageRate));
if (avgUsageRate > 80) {
report.append(" ⚠️ 内存使用率较高");
}
report.append("\n\n");
}
private void generateThreadAnalysis(StringBuilder report) {
report.append("## 线程分析\n\n");
List<PerformanceSnapshot> snapshotList = new ArrayList<>(snapshots.values());
// 线程数变化分析
int minThreads = snapshotList.stream().mapToInt(s -> s.threadCount).min().orElse(0);
int maxThreads = snapshotList.stream().mapToInt(s -> s.threadCount).max().orElse(0);
int peakThreads = snapshotList.stream().mapToInt(s -> s.peakThreadCount).max().orElse(0);
report.append("**线程数统计**:\n");
report.append("- 最小线程数: ").append(minThreads).append("\n");
report.append("- 最大线程数: ").append(maxThreads).append("\n");
report.append("- 历史峰值线程数: ").append(peakThreads).append("\n");
int threadVariation = maxThreads - minThreads;
if (threadVariation > 50) {
report.append("\n⚠️ 线程数变化较大,可能存在线程池配置问题\n");
}
report.append("\n");
}
private void generateClassLoadingAnalysis(StringBuilder report) {
report.append("## 类加载分析\n\n");
List<PerformanceSnapshot> snapshotList = new ArrayList<>(snapshots.values());
if (!snapshotList.isEmpty()) {
PerformanceSnapshot latest = snapshotList.get(snapshotList.size() - 1);
report.append("**类加载统计**:\n");
report.append("- 当前已加载类数: ").append(latest.loadedClassCount).append("\n");
report.append("- 总共加载类数: ").append(latest.totalLoadedClassCount).append("\n");
report.append("- 已卸载类数: ").append(latest.unloadedClassCount).append("\n");
if (latest.unloadedClassCount == 0 && latest.totalLoadedClassCount > 10000) {
report.append("\n⚠️ 没有类被卸载,可能存在类加载器泄漏\n");
}
}
report.append("\n");
}
private void generateTrendAnalysis(StringBuilder report) {
report.append("## 趋势分析\n\n");
List<PerformanceSnapshot> snapshotList = new ArrayList<>(snapshots.values());
snapshotList.sort(Comparator.comparing(s -> s.timestamp));
if (snapshotList.size() >= 3) {
// 分析最近的趋势
int recentCount = Math.min(10, snapshotList.size());
List<PerformanceSnapshot> recentSnapshots = snapshotList.subList(
snapshotList.size() - recentCount, snapshotList.size());
// 内存趋势
double memoryTrend = calculateTrend(recentSnapshots, s -> (double)s.heapUsed);
report.append("**最近趋势**:\n");
report.append("- 内存使用趋势: ");
if (memoryTrend > 1000000) { // 1MB/sample
report.append("上升 📈");
} else if (memoryTrend < -1000000) {
report.append("下降 📉");
} else {
report.append("稳定 ➡️");
}
report.append("\n");
// 线程趋势
double threadTrend = calculateTrend(recentSnapshots, s -> (double)s.threadCount);
report.append("- 线程数趋势: ");
if (threadTrend > 0.5) {
report.append("上升 📈");
} else if (threadTrend < -0.5) {
report.append("下降 📉");
} else {
report.append("稳定 ➡️");
}
report.append("\n");
}
report.append("\n");
}
private double calculateTrend(List<PerformanceSnapshot> snapshots,
java.util.function.Function<PerformanceSnapshot, Double> valueExtractor) {
if (snapshots.size() < 2) return 0;
// 简单线性回归计算趋势
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
int n = snapshots.size();
for (int i = 0; i < n; i++) {
double x = i;
double y = valueExtractor.apply(snapshots.get(i));
sumX += x;
sumY += y;
sumXY += x * y;
sumX2 += x * x;
}
// 计算斜率
double slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
return slope;
}
private String formatBytes(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024));
return String.format("%.1f GB", bytes / (1024.0 * 1024 * 1024));
}
public void exportReport(File file) throws IOException {
String report = generateReport();
try (FileWriter writer = new FileWriter(file)) {
writer.write(report);
}
}
// 性能快照数据类
private static class PerformanceSnapshot {
long timestamp;
long heapUsed;
long heapMax;
long nonHeapUsed;
int threadCount;
int peakThreadCount;
int loadedClassCount;
long totalLoadedClassCount;
long unloadedClassCount;
}
}
插件调试与测试
调试环境配置
1. NetBeans调试配置
<!-- nbproject/project.properties -->
# 调试配置
run.args.extra=-J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
# 日志配置
run.args.extra=${run.args.extra} -J-Djava.util.logging.config.file=logging.properties
# 开发模式
run.args.extra=${run.args.extra} -J-Dvisualvm.development.mode=true
2. 日志配置
# logging.properties
# 全局日志级别
.level=INFO
# 控制台处理器
handlers=java.util.logging.ConsoleHandler
# 控制台处理器配置
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
# 自定义插件日志
org.example.visualvm.level=FINE
org.example.visualvm.handlers=java.util.logging.FileHandler
org.example.visualvm.useParentHandlers=false
# 文件处理器
java.util.logging.FileHandler.pattern=visualvm-plugin.log
java.util.logging.FileHandler.limit=1000000
java.util.logging.FileHandler.count=5
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
单元测试
1. 测试框架配置
<!-- pom.xml 测试依赖 -->
<dependencies>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.6.1</version>
<scope>test</scope>
</dependency>
<!-- NetBeans Test Utilities -->
<dependency>
<groupId>org.netbeans.api</groupId>
<artifactId>org-netbeans-modules-nbjunit</artifactId>
<version>${netbeans.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
2. 数据收集器测试
// CustomDataCollectorTest.java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
class CustomDataCollectorTest {
@Mock
private Application mockApplication;
@Mock
private Jvm mockJvm;
private CustomDataCollector collector;
private AutoCloseable mocks;
@BeforeEach
void setUp() {
mocks = MockitoAnnotations.openMocks(this);
collector = new CustomDataCollector(mockApplication);
// 配置模拟对象
when(JvmFactory.getJVMFor(mockApplication)).thenReturn(mockJvm);
when(mockJvm.isMemoryMonitoringSupported()).thenReturn(true);
when(mockJvm.isThreadMonitoringSupported()).thenReturn(true);
}
@AfterEach
void tearDown() throws Exception {
collector.stopCollection();
mocks.close();
}
@Test
void testDataCollection() throws InterruptedException {
// 配置模拟数据
MemoryUsage heapUsage = new MemoryUsage(0, 1000000, 2000000, 4000000);
when(mockJvm.getHeapMemoryUsage()).thenReturn(heapUsage);
when(mockJvm.getThreadCount()).thenReturn(10);
// 设置监听器
CountDownLatch latch = new CountDownLatch(1);
CustomDataCollector.DataCollectionListener listener = metrics -> {
assertNotNull(metrics);
assertEquals(1000000, metrics.heapUsed);
assertEquals(4000000, metrics.heapMax);
assertEquals(10, metrics.threadCount);
latch.countDown();
};
collector.addListener(listener);
collector.startCollection();
// 等待数据收集
assertTrue(latch.await(10, TimeUnit.SECONDS), "数据收集超时");
}
@Test
void testErrorHandling() {
// 模拟JVM异常
when(JvmFactory.getJVMFor(mockApplication)).thenThrow(new RuntimeException("JVM错误"));
// 验证异常处理
assertDoesNotThrow(() -> {
collector.startCollection();
Thread.sleep(100); // 等待一次收集
});
}
@Test
void testListenerManagement() {
CustomDataCollector.DataCollectionListener listener1 = mock(CustomDataCollector.DataCollectionListener.class);
CustomDataCollector.DataCollectionListener listener2 = mock(CustomDataCollector.DataCollectionListener.class);
// 添加监听器
collector.addListener(listener1);
collector.addListener(listener2);
// 移除监听器
collector.removeListener(listener1);
// 验证只有listener2被调用
// 这里需要触发数据收集并验证调用
}
}
3. 视图组件测试
// CustomViewTest.java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.netbeans.junit.NbTestCase;
import javax.swing.SwingUtilities;
import java.awt.Component;
class CustomViewTest extends NbTestCase {
private Application mockApplication;
private CustomView view;
public CustomViewTest(String name) {
super(name);
}
@BeforeEach
@Override
protected void setUp() throws Exception {
super.setUp();
mockApplication = mock(Application.class);
when(mockApplication.getId()).thenReturn("test-app");
SwingUtilities.invokeAndWait(() -> {
view = new CustomView(mockApplication);
});
}
@Test
public void testViewCreation() throws Exception {
SwingUtilities.invokeAndWait(() -> {
DataViewComponent component = view.createComponent();
assertNotNull("视图组件不应为null", component);
Component viewComponent = component.getView();
assertNotNull("视图不应为null", viewComponent);
});
}
@Test
public void testDataRefresh() throws Exception {
SwingUtilities.invokeAndWait(() -> {
// 测试数据刷新功能
view.createComponent();
// 这里可以测试刷新按钮的功能
});
}
}
集成测试
1. 插件加载测试
// PluginIntegrationTest.java
import org.netbeans.junit.NbModuleSuite;
import org.netbeans.junit.NbTestCase;
import junit.framework.Test;
import org.openide.util.Lookup;
public class PluginIntegrationTest extends NbTestCase {
public PluginIntegrationTest(String name) {
super(name);
}
public static Test suite() {
return NbModuleSuite.createConfiguration(PluginIntegrationTest.class)
.addTest("testPluginServices")
.addTest("testViewProviderRegistration")
.enableModules(".*")
.gui(false)
.suite();
}
public void testPluginServices() {
// 测试服务注册
CustomViewProvider provider = Lookup.getDefault().lookup(CustomViewProvider.class);
assertNotNull("CustomViewProvider应该被注册", provider);
CustomApplicationType.Factory factory = Lookup.getDefault().lookup(CustomApplicationType.Factory.class);
assertNotNull("ApplicationTypeFactory应该被注册", factory);
}
public void testViewProviderRegistration() {
// 测试视图提供者
Collection<? extends DataSourceViewProvider> providers =
Lookup.getDefault().lookupAll(DataSourceViewProvider.class);
boolean found = providers.stream()
.anyMatch(p -> p instanceof CustomViewProvider);
assertTrue("CustomViewProvider应该在查找中找到", found);
}
}
性能测试
1. 内存泄漏测试
// MemoryLeakTest.java
import org.junit.jupiter.api.Test;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
class MemoryLeakTest {
@Test
void testDataCollectorMemoryLeak() {
List<WeakReference<CustomDataCollector>> collectors = new ArrayList<>();
// 创建多个收集器实例
for (int i = 0; i < 100; i++) {
Application app = mock(Application.class);
CustomDataCollector collector = new CustomDataCollector(app);
collector.startCollection();
collector.stopCollection();
collectors.add(new WeakReference<>(collector));
}
// 强制垃圾回收
System.gc();
System.runFinalization();
System.gc();
// 检查是否有内存泄漏
long aliveCount = collectors.stream()
.mapToLong(ref -> ref.get() != null ? 1 : 0)
.sum();
assertTrue(aliveCount < 10, "可能存在内存泄漏,存活对象数: " + aliveCount);
}
}
2. 性能基准测试
// PerformanceBenchmarkTest.java
import org.junit.jupiter.api.Test;
import java.util.concurrent.TimeUnit;
class PerformanceBenchmarkTest {
@Test
void testDataCollectionPerformance() {
Application app = mock(Application.class);
Jvm jvm = mock(Jvm.class);
when(JvmFactory.getJVMFor(app)).thenReturn(jvm);
when(jvm.isMemoryMonitoringSupported()).thenReturn(true);
when(jvm.getHeapMemoryUsage()).thenReturn(new MemoryUsage(0, 1000000, 2000000, 4000000));
CustomDataCollector collector = new CustomDataCollector(app);
// 性能测试
long startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
// 模拟数据收集
collector.collectData(); // 假设有这个方法
}
long endTime = System.nanoTime();
long duration = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
assertTrue(duration < 1000, "数据收集性能测试失败,耗时: " + duration + "ms");
}
}
插件部署最佳实践
版本管理
1. 语义化版本控制
<!-- pom.xml -->
<version>1.2.3</version>
<!--
1: 主版本号 - 不兼容的API变更
2: 次版本号 - 向后兼容的功能性新增
3: 修订号 - 向后兼容的问题修正
-->
2. 版本兼容性检查
// VersionCompatibility.java
public class VersionCompatibility {
private static final String MIN_VISUALVM_VERSION = "2.1.0";
private static final String MAX_VISUALVM_VERSION = "2.9.9";
public static boolean isCompatible() {
String currentVersion = getVisualVMVersion();
return isVersionInRange(currentVersion, MIN_VISUALVM_VERSION, MAX_VISUALVM_VERSION);
}
private static String getVisualVMVersion() {
// 获取当前VisualVM版本
return System.getProperty("visualvm.version", "unknown");
}
private static boolean isVersionInRange(String version, String min, String max) {
// 实现版本比较逻辑
return compareVersions(version, min) >= 0 && compareVersions(version, max) <= 0;
}
private static int compareVersions(String v1, String v2) {
String[] parts1 = v1.split("\\.");
String[] parts2 = v2.split("\\.");
int maxLength = Math.max(parts1.length, parts2.length);
for (int i = 0; i < maxLength; i++) {
int part1 = i < parts1.length ? Integer.parseInt(parts1[i]) : 0;
int part2 = i < parts2.length ? Integer.parseInt(parts2[i]) : 0;
if (part1 != part2) {
return Integer.compare(part1, part2);
}
}
return 0;
}
}
自动化部署
1. GitHub Actions工作流
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
release:
types: [ published ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 8
uses: actions/setup-java@v3
with:
java-version: '8'
distribution: 'temurin'
- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- name: Run tests
run: mvn clean test
- name: Generate test report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Maven Tests
path: target/surefire-reports/*.xml
reporter: java-junit
build:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'release'
steps:
- uses: actions/checkout@v3
- name: Set up JDK 8
uses: actions/setup-java@v3
with:
java-version: '8'
distribution: 'temurin'
- name: Build plugin
run: mvn clean package
- name: Upload NBM to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./target/custom-visualvm-plugin-${{ github.event.release.tag_name }}.nbm
asset_name: custom-visualvm-plugin-${{ github.event.release.tag_name }}.nbm
asset_content_type: application/octet-stream
- name: Update plugin center
run: |
# 更新插件中心的脚本
./scripts/update-plugin-center.sh ${{ github.event.release.tag_name }}
2. 插件中心更新脚本
#!/bin/bash
# scripts/update-plugin-center.sh
VERSION=$1
PLUGIN_NAME="custom-visualvm-plugin"
UPDATE_CENTER_REPO="your-org/visualvm-plugin-center"
if [ -z "$VERSION" ]; then
echo "错误: 请提供版本号"
exit 1
fi
# 克隆插件中心仓库
git clone https://github.com/${UPDATE_CENTER_REPO}.git plugin-center
cd plugin-center
# 更新插件信息
cat > plugins/${PLUGIN_NAME}.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<module codenamebase="org.example.visualvm.custom"
distribution="${PLUGIN_NAME}-${VERSION}.nbm"
downloadsize="$(stat -c%s ../target/${PLUGIN_NAME}-${VERSION}.nbm)"
homepage="https://github.com/your-org/custom-visualvm-plugin"
license="Apache-2.0"
moduleauthor="Your Name"
needsrestart="true"
releasedate="$(date +'%Y/%m/%d')">
<manifest OpenIDE-Module="org.example.visualvm.custom"
OpenIDE-Module-Display-Category="Monitoring"
OpenIDE-Module-Implementation-Version="${VERSION}"
OpenIDE-Module-Long-Description="自定义VisualVM监控插件"
OpenIDE-Module-Name="Custom VisualVM Plugin"
OpenIDE-Module-Short-Description="自定义监控插件"
OpenIDE-Module-Specification-Version="${VERSION}"/>
</module>
EOF
# 提交更改
git add plugins/${PLUGIN_NAME}.xml
git commit -m "Update ${PLUGIN_NAME} to version ${VERSION}"
git push origin main
echo "插件中心已更新到版本 ${VERSION}"
用户文档
1. 安装指南
# Custom VisualVM Plugin 安装指南
## 系统要求
- VisualVM 2.1.0 或更高版本
- Java 8 或更高版本
## 安装方法
### 方法1: 通过VisualVM插件管理器
1. 启动VisualVM
2. 选择 "工具" -> "插件"
3. 在 "可用插件" 标签页中搜索 "Custom Plugin"
4. 选择插件并点击 "安装"
5. 按照向导完成安装
6. 重启VisualVM
### 方法2: 手动安装
1. 从 [Releases页面](https://github.com/your-org/custom-visualvm-plugin/releases) 下载最新的NBM文件
2. 在VisualVM中选择 "工具" -> "插件"
3. 切换到 "已下载" 标签页
4. 点击 "添加插件" 按钮
5. 选择下载的NBM文件
6. 按照向导完成安装
## 使用说明
安装完成后,插件会在以下位置出现:
- 应用程序树中会显示支持的应用程序类型
- 选择应用程序后,会在右侧面板中显示 "Custom View" 标签页
- 可以通过工具栏按钮进行配置和数据刷新
## 故障排除
### 插件未显示
1. 确认VisualVM版本兼容性
2. 检查插件是否正确安装
3. 查看VisualVM日志文件
### 性能问题
1. 调整监控间隔设置
2. 减少数据保留时间
3. 关闭不需要的监控功能
2. API文档
/**
* Custom VisualVM Plugin API 文档
*
* 本插件提供以下主要功能:
*
* 1. 自定义应用程序类型识别
* 2. 实时性能数据收集
* 3. 可视化图表展示
* 4. 性能报告生成
*
* @author Your Name
* @version 1.0.0
* @since 2023-12-01
*/
public class PluginAPI {
/**
* 获取数据收集器实例
*
* @param application 目标应用程序
* @return 数据收集器实例
* @throws IllegalArgumentException 如果应用程序不支持
*/
public static CustomDataCollector getDataCollector(Application application) {
// 实现代码
}
/**
* 创建性能报告生成器
*
* @param application 目标应用程序
* @return 报告生成器实例
*/
public static PerformanceReportGenerator createReportGenerator(Application application) {
// 实现代码
}
}
本章总结
关键要点
VisualVM插件架构
- 基于NetBeans平台的模块化架构
- 应用程序类型、数据源、视图等核心组件
- 服务发现和注册机制
插件开发环境
- NetBeans IDE和Maven构建工具
- 项目结构和依赖配置
- 服务注册和模块描述
高级功能开发
- 自定义数据收集器
- 图表组件集成
- 配置管理系统
- 外部API集成
测试与调试
- 单元测试和集成测试
- 性能测试和内存泄漏检测
- 调试环境配置
插件部署和分发
- NBM文件构建和安装
- 版本管理和兼容性检查
- 自动化部署流程
最佳实践
设计原则
- 遵循单一职责原则
- 提供清晰的用户界面
- 处理异常和错误情况
- 版本兼容性考虑
性能考虑
- 异步数据收集
- 合理的刷新频率
- 内存使用优化
- 资源清理和释放
用户体验
- 直观的界面设计
- 丰富的配置选项
- 有用的错误提示
- 完善的文档
维护性
- 模块化代码结构
- 完善的测试覆盖
- 自动化构建和部署
- 持续集成和质量保证
下一章预告
下一章我们将学习VisualVM最佳实践与案例分析,包括: - 生产环境监控策略 - 性能问题诊断案例 - 监控数据分析方法 - 团队协作和知识分享
通过实际案例,您将学会如何在真实项目中有效使用VisualVM进行性能监控和问题诊断。