JVM嵌入式优化 #
一、嵌入式环境下的JVM挑战 #
1.1 资源限制 #
嵌入式设备通常面临以下资源限制:
| 资源 | 典型限制 | JVM影响 |
|---|---|---|
| 内存 | 256MB-2GB | 堆内存受限,GC压力大 |
| 存储 | 8GB-32GB | JVM体积需精简 |
| CPU | 1-4核 | 计算性能有限 |
| 功耗 | 严格限制 | 需要低功耗运行 |
1.2 JVM内存模型 #
text
┌─────────────────────────────────────────────────┐
│ JVM内存结构 │
├─────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────┐ │
│ │ 堆内存 (Heap) │ │
│ │ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ 年轻代 │ │ 老年代 │ │ │
│ │ │ (Young) │ │ (Old) │ │ │
│ │ └─────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ 非堆内存 (Non-Heap) │ │
│ │ ├── 元空间 (Metaspace) │ │
│ │ ├── 代码缓存 (Code Cache) │ │
│ │ ├── 线程栈 (Thread Stacks) │ │
│ │ └── 直接内存 (Direct Memory) │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
二、内存优化 #
2.1 堆内存配置 #
基础配置
bash
# 设置堆内存大小
java -Xms128m -Xmx256m -jar app.jar
# 参数说明
# -Xms: 初始堆大小
# -Xmx: 最大堆大小
嵌入式推荐配置
bash
# 512MB内存设备
java -Xms64m -Xmx192m -XX:MaxMetaspaceSize=64m -jar app.jar
# 1GB内存设备
java -Xms128m -Xmx384m -XX:MaxMetaspaceSize=96m -jar app.jar
# 2GB内存设备
java -Xms256m -Xmx768m -XX:MaxMetaspaceSize=128m -jar app.jar
2.2 堆内存比例优化 #
bash
# 年轻代与老年代比例
-XX:NewRatio=2 # 老年代:年轻代 = 2:1
# Survivor区比例
-XX:SurvivorRatio=8 # Eden:Survivor = 8:1
# 示例:256MB堆内存
# 老年代: 170MB
# 年轻代: 85MB
# Eden: 68MB
# Survivor: 8.5MB each
2.3 元空间优化 #
bash
# 限制元空间大小
-XX:MetaspaceSize=32m
-XX:MaxMetaspaceSize=64m
# 禁用类元数据压缩(节省内存)
-XX:-UseCompressedClassPointers
2.4 代码缓存优化 #
bash
# 设置代码缓存大小
-XX:InitialCodeCacheSize=4m
-XX:ReservedCodeCacheSize=32m
# 分层编译优化
-XX:CompileThreshold=10000
2.5 线程栈优化 #
bash
# 减小线程栈大小(默认1MB)
-Xss256k
# 对于大量线程的应用
-Xss128k
三、垃圾回收优化 #
3.1 GC选择 #
Serial GC(推荐小内存设备)
bash
# 适用于单核或小内存设备
-XX:+UseSerialGC
# 特点:
# - 单线程GC
# - 内存占用小
# - 适合客户端应用
G1 GC(推荐大内存设备)
bash
# 适用于多核、大内存设备
-XX:+UseG1GC
# G1调优参数
-XX:MaxGCPauseMillis=100 # 目标最大暂停时间
-XX:G1HeapRegionSize=4m # Region大小
-XX:InitiatingHeapOccupancyPercent=45 # 触发并发GC的堆占用率
ZGC(Java 17+,大内存设备)
bash
# 适用于大内存、低延迟需求
-XX:+UseZGC
-XX:ZCollectionInterval=5 # GC间隔(秒)
3.2 GC日志配置 #
bash
# Java 17 GC日志配置
-Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10m
# 分析GC日志
# 使用工具:GCViewer、GCEasy.io
3.3 内存监控代码 #
java
package com.example.monitor;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.List;
public class MemoryMonitor {
private final MemoryMXBean memoryBean;
private final List<GarbageCollectorMXBean> gcBeans;
public MemoryMonitor() {
this.memoryBean = ManagementFactory.getMemoryMXBean();
this.gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
}
public void printMemoryInfo() {
MemoryUsage heap = memoryBean.getHeapMemoryUsage();
MemoryUsage nonHeap = memoryBean.getNonHeapMemoryUsage();
System.out.println("=== 内存使用情况 ===");
System.out.printf("堆内存: 已用=%dMB, 已分配=%dMB, 最大=%dMB%n",
toMB(heap.getUsed()),
toMB(heap.getCommitted()),
toMB(heap.getMax()));
System.out.printf("非堆内存: 已用=%dMB, 已分配=%dMB%n",
toMB(nonHeap.getUsed()),
toMB(nonHeap.getCommitted()));
}
public void printGCInfo() {
System.out.println("=== GC统计 ===");
for (GarbageCollectorMXBean gc : gcBeans) {
System.out.printf("%s: 次数=%d, 总时间=%dms%n",
gc.getName(),
gc.getCollectionCount(),
gc.getCollectionTime());
}
}
public MemoryStats getStats() {
MemoryUsage heap = memoryBean.getHeapMemoryUsage();
long gcCount = 0;
long gcTime = 0;
for (GarbageCollectorMXBean gc : gcBeans) {
gcCount += gc.getCollectionCount();
gcTime += gc.getCollectionTime();
}
return new MemoryStats(
toMB(heap.getUsed()),
toMB(heap.getCommitted()),
toMB(heap.getMax()),
gcCount,
gcTime
);
}
private long toMB(long bytes) {
return bytes / (1024 * 1024);
}
public static class MemoryStats {
public final long heapUsedMB;
public final long heapCommittedMB;
public final long heapMaxMB;
public final long gcCount;
public final long gcTimeMs;
public MemoryStats(long heapUsedMB, long heapCommittedMB,
long heapMaxMB, long gcCount, long gcTimeMs) {
this.heapUsedMB = heapUsedMB;
this.heapCommittedMB = heapCommittedMB;
this.heapMaxMB = heapMaxMB;
this.gcCount = gcCount;
this.gcTimeMs = gcTimeMs;
}
}
}
四、启动优化 #
4.1 类数据共享(CDS) #
bash
# 创建共享归档
java -Xshare:dump
# 使用共享归档启动
java -Xshare:on -jar app.jar
# 自定义共享归档
java -XX:SharedArchiveFile=app.jsa -jar app.jar
4.2 应用类数据共享(AppCDS) #
bash
# 1. 生成类列表
java -Xshare:off -XX:+UseAppCDS -XX:SharedClassListFile=classes.lst \
-cp app.jar com.example.Main
# 2. 创建共享归档
java -Xshare:dump -XX:+UseAppCDS \
-XX:SharedClassListFile=classes.lst \
-XX:SharedArchiveFile=app.jsa \
-cp app.jar
# 3. 使用共享归档启动
java -Xshare:on -XX:+UseAppCDS \
-XX:SharedArchiveFile=app.jsa \
-cp app.jar com.example.Main
4.3 延迟类加载 #
bash
# 启用延迟类加载(Java 21+)
-XX:+ClassLoadersParallelRegistration
4.4 启动时间监控 #
java
package com.example.monitor;
public class StartupMonitor {
public static void logStartupPhase(String phase) {
long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
System.out.printf("[%dms] %s%n", uptime, phase);
}
public static void main(String[] args) {
logStartupPhase("应用启动开始");
logStartupPhase("初始化配置");
logStartupPhase("连接硬件");
logStartupPhase("启动网络服务");
logStartupPhase("应用启动完成");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
logStartupPhase("应用关闭");
}));
}
}
五、GraalVM原生镜像 #
5.1 GraalVM简介 #
GraalVM可以将Java应用编译为原生可执行文件,实现:
- 毫秒级启动
- 更低内存占用
- 更小体积
5.2 安装GraalVM #
bash
# 使用SDKMAN安装
sdk install java 17.0.9-graalce
# 设置为默认
sdk use java 17.0.9-graalce
# 安装native-image
gu install native-image
5.3 构建原生镜像 #
项目配置
xml
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>17.0.9</version>
<scope>provided</scope>
</dependency>
</dependencies>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.28</version>
<configuration>
<mainClass>com.example.Main</mainClass>
<buildArgs>
<buildArg>--no-fallback</buildArg>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
反射配置
json
// META-INF/native-image/reflect-config.json
[
{
"name": "com.example.Sensor",
"allDeclaredConstructors": true,
"allPublicMethods": true,
"allDeclaredFields": true
}
]
构建命令
bash
# 构建原生镜像
mvn -Pnative package
# 生成的可执行文件位于 target/ 目录
5.4 原生镜像优化 #
bash
# 构建优化参数
native-image \
--no-fallback \
--enable-preview \
-H:+ReportExceptionStackTraces \
-H:ConfigurationFileDirectories=src/main/resources/META-INF/native-image \
-H:Name=embedded-app \
-H:Class=com.example.Main \
-H:EnableURLProtocols=http,https \
--initialize-at-build-time=org.slf4j \
--initialize-at-run-time=com.pi4j \
-jar app.jar
六、性能监控与调优 #
6.1 JMX监控 #
bash
# 启用JMX远程监控
java -Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.local.only=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar app.jar
# 使用jconsole或VisualVM连接
6.2 性能分析代码 #
java
package com.example.monitor;
import java.util.concurrent.atomic.AtomicLong;
public class PerformanceMonitor {
private final AtomicLong operationCount = new AtomicLong(0);
private final AtomicLong totalLatency = new AtomicLong(0);
private final AtomicLong maxLatency = new AtomicLong(0);
public void recordOperation(long latencyMs) {
operationCount.incrementAndGet();
totalLatency.addAndGet(latencyMs);
maxLatency.updateAndGet(current -> Math.max(current, latencyMs));
}
public PerformanceStats getStats() {
long count = operationCount.get();
long total = totalLatency.get();
return new PerformanceStats(
count,
count > 0 ? (double) total / count : 0,
maxLatency.get()
);
}
public void reset() {
operationCount.set(0);
totalLatency.set(0);
maxLatency.set(0);
}
public static class PerformanceStats {
public final long operationCount;
public final double avgLatencyMs;
public final long maxLatencyMs;
public PerformanceStats(long operationCount, double avgLatencyMs, long maxLatencyMs) {
this.operationCount = operationCount;
this.avgLatencyMs = avgLatencyMs;
this.maxLatencyMs = maxLatencyMs;
}
@Override
public String toString() {
return String.format("操作次数: %d, 平均延迟: %.2fms, 最大延迟: %dms",
operationCount, avgLatencyMs, maxLatencyMs);
}
}
}
6.3 系统资源监控 #
java
package com.example.monitor;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class SystemMonitor {
public static class CpuStats {
public final double usage;
public final double temperature;
public CpuStats(double usage, double temperature) {
this.usage = usage;
this.temperature = temperature;
}
}
public static class DiskStats {
public final long totalKB;
public final long usedKB;
public final long availableKB;
public DiskStats(long totalKB, long usedKB, long availableKB) {
this.totalKB = totalKB;
this.usedKB = usedKB;
this.availableKB = availableKB;
}
}
public CpuStats getCpuStats() {
double usage = getCpuUsage();
double temp = getCpuTemperature();
return new CpuStats(usage, temp);
}
private double getCpuUsage() {
try {
Process process = Runtime.getRuntime().exec("top -bn1 | grep 'Cpu(s)'");
BufferedReader reader = new BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
String line = reader.readLine();
if (line != null) {
String[] parts = line.split("\\s+");
return Double.parseDouble(parts[1].replace(",", "."));
}
} catch (Exception e) {
// 忽略错误
}
return 0;
}
private double getCpuTemperature() {
try (BufferedReader reader = new BufferedReader(
new FileReader("/sys/class/thermal/thermal_zone0/temp"))) {
String temp = reader.readLine();
return Double.parseDouble(temp) / 1000.0;
} catch (IOException e) {
return 0;
}
}
public DiskStats getDiskStats(String path) {
try {
Process process = Runtime.getRuntime().exec("df -k " + path);
BufferedReader reader = new BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
reader.readLine();
String line = reader.readLine();
if (line != null) {
String[] parts = line.split("\\s+");
return new DiskStats(
Long.parseLong(parts[1]),
Long.parseLong(parts[2]),
Long.parseLong(parts[3])
);
}
} catch (Exception e) {
// 忽略错误
}
return new DiskStats(0, 0, 0);
}
}
七、完整优化配置示例 #
7.1 启动脚本 #
bash
#!/bin/bash
# start.sh - 优化后的启动脚本
APP_NAME="embedded-app"
JAR_FILE="app.jar"
LOG_DIR="/var/log/$APP_NAME"
PID_FILE="/var/run/$APP_NAME.pid"
mkdir -p $LOG_DIR
JVM_OPTS="-server \
-Xms128m -Xmx256m \
-XX:MaxMetaspaceSize=64m \
-XX:+UseSerialGC \
-XX:+UseStringDeduplication \
-XX:CompileThreshold=10000 \
-Xss256k \
-XX:+UseCompressedOops \
-XX:+UseCompressedClassPointers \
-XX:+OptimizeStringConcat \
-XX:+UseCMoveUnconditionally \
-Xshare:on \
-XX:+ExitOnOutOfMemoryError \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=$LOG_DIR/heapdump.hprof \
-Xlog:gc*:file=$LOG_DIR/gc.log:time,uptime,level,tags:filecount=5,filesize=10m \
-Djava.security.egd=file:/dev/./urandom \
-Djava.awt.headless=true"
start() {
if [ -f $PID_FILE ]; then
echo "Application is already running"
exit 1
fi
echo "Starting $APP_NAME..."
nohup java $JVM_OPTS -jar $JAR_FILE > $LOG_DIR/app.log 2>&1 &
echo $! > $PID_FILE
echo "Started with PID $(cat $PID_FILE)"
}
stop() {
if [ ! -f $PID_FILE ]; then
echo "Application is not running"
exit 1
fi
echo "Stopping $APP_NAME..."
kill $(cat $PID_FILE)
rm $PID_FILE
echo "Stopped"
}
status() {
if [ -f $PID_FILE ]; then
echo "$APP_NAME is running with PID $(cat $PID_FILE)"
else
echo "$APP_NAME is not running"
fi
}
case "$1" in
start) start ;;
stop) stop ;;
status) status ;;
restart) stop; start ;;
*) echo "Usage: $0 {start|stop|status|restart}" ;;
esac
八、优化检查清单 #
8.1 内存优化 #
- [ ] 根据设备内存设置合适的堆大小
- [ ] 选择合适的GC算法
- [ ] 配置元空间大小
- [ ] 减小线程栈大小
- [ ] 启用字符串去重
8.2 启动优化 #
- [ ] 启用类数据共享(CDS)
- [ ] 考虑使用GraalVM原生镜像
- [ ] 延迟加载非必要组件
- [ ] 优化类路径
8.3 运行时优化 #
- [ ] 启用JMX监控
- [ ] 配置GC日志
- [ ] 设置OOM处理策略
- [ ] 添加性能监控代码
九、总结 #
JVM嵌入式优化的关键点:
- 内存配置:根据设备资源合理设置堆内存和非堆内存
- GC选择:小内存用Serial GC,大内存用G1或ZGC
- 启动优化:使用CDS或GraalVM原生镜像加速启动
- 监控告警:添加内存和性能监控,及时发现问题
下一章我们将学习GPIO编程,深入了解硬件交互技术。
最后更新:2026-03-27