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嵌入式优化的关键点:

  1. 内存配置:根据设备资源合理设置堆内存和非堆内存
  2. GC选择:小内存用Serial GC,大内存用G1或ZGC
  3. 启动优化:使用CDS或GraalVM原生镜像加速启动
  4. 监控告警:添加内存和性能监控,及时发现问题

下一章我们将学习GPIO编程,深入了解硬件交互技术。

最后更新:2026-03-27