Google Cloud Run与JVM:重启时如何保存heap dumps、thread dumps及gc logs
在Google Cloud Run的JVM应用中收集Heap Dumps、Thread Dumps并上传至Cloud Storage
一、前置准备:配置GCS权限
给Cloud Run服务关联的服务账号添加roles/storage.objectCreator角色,确保它拥有向目标GCS存储桶上传文件的权限。
二、捕获各类诊断文件的方法
1. GC日志
启动JVM时直接指定参数,将GC日志输出到Cloud Run可写的临时目录/tmp:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/tmp/gc.log
日志会随应用运行持续生成,后续可直接上传该文件。
2. Heap Dumps
- 自动触发(OOM场景):添加JVM参数,在内存溢出时自动生成Heap Dump:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof - 手动触发(终止时):通过
jmap命令生成(需容器镜像包含JDK工具,而非仅JRE):
Cloud Run容器内主进程PID通常为1,直接用jmap -dump:format=b,file=/tmp/heapdump.hprof 11即可。
3. Thread Dumps
使用jstack命令生成,同样指定PID为1:
jstack 1 > /tmp/threaddump.txt
三、处理终止信号,自动上传文件
Cloud Run在终止容器前会发送SIGTERM信号,提供最长90秒(可配置)的优雅关闭窗口。我们可以通过两种方式捕获该信号,完成文件生成与上传。
方法1:Shell脚本作为入口点处理信号
将Java启动逻辑封装进Shell脚本,添加信号处理逻辑:
#!/bin/bash # 启动JVM应用,带上GC日志和OOM堆转储参数 java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/tmp/gc.log \ -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof \ -jar your-app.jar & APP_PID=$! # 定义关闭处理函数 handle_shutdown() { echo "开始收集诊断文件..." # 生成线程转储 jstack $APP_PID > /tmp/threaddump.txt # 若未因OOM生成堆转储,手动触发 if [ ! -f /tmp/heapdump.hprof ]; then jmap -dump:format=b,file=/tmp/heapdump.hprof $APP_PID fi # 上传文件到GCS,添加时间戳区分版本 gsutil cp /tmp/gc.log gs://your-bucket/diag/gc-$(date +%Y%m%d-%H%M%S).log gsutil cp /tmp/heapdump.hprof gs://your-bucket/diag/heapdump-$(date +%Y%m%d-%H%M%S).hprof gsutil cp /tmp/threaddump.txt gs://your-bucket/diag/threaddump-$(date +%Y%m%d-%H%M%S).txt # 等待应用进程正常退出 wait $APP_PID } # 捕获SIGTERM信号 trap handle_shutdown SIGTERM # 等待应用主进程结束 wait $APP_PID
- 确保容器镜像安装了
bash、gsutil(来自gcloud SDK)以及JDK工具; - 在Dockerfile中设置
ENTRYPOINT ["/path/to/start.sh"],将脚本作为容器入口。
方法2:Java应用内部添加Shutdown Hook
在Java代码中注册Shutdown Hook,捕获JVM关闭事件并完成操作:
import com.google.cloud.storage.BlobId; import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; import java.nio.file.Files; public class YourApp { public static void main(String[] args) { // 注册Shutdown Hook Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { String tmpDir = System.getProperty("java.io.tmpdir"); // 生成线程转储 ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); String threadDump = threadBean.dumpAllThreads(true, true); File threadDumpFile = new File(tmpDir, "threaddump.txt"); try (FileOutputStream fos = new FileOutputStream(threadDumpFile)) { fos.write(threadDump.getBytes()); } // 生成堆转储(需依赖HotSpot JVM) File heapDumpFile = new File(tmpDir, "heapdump.hprof"); com.sun.management.HotSpotDiagnosticMXBean hsBean = ManagementFactory.getPlatformMXBean(com.sun.management.HotSpotDiagnosticMXBean.class); hsBean.dumpHeap(heapDumpFile.getAbsolutePath(), true); // 上传所有文件到GCS Storage storage = StorageOptions.getDefaultInstance().getService(); String bucketName = "your-bucket"; uploadFile(storage, bucketName, new File(tmpDir, "gc.log"), "diag/gc-" + System.currentTimeMillis() + ".log"); uploadFile(storage, bucketName, heapDumpFile, "diag/heapdump-" + System.currentTimeMillis() + ".hprof"); uploadFile(storage, bucketName, threadDumpFile, "diag/threaddump-" + System.currentTimeMillis() + ".txt"); } catch (Exception e) { e.printStackTrace(); } })); // 启动应用核心逻辑 // ... } private static void uploadFile(Storage storage, String bucketName, File file, String destPath) throws IOException { BlobId blobId = BlobId.of(bucketName, destPath); BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); storage.create(blobInfo, Files.readAllBytes(file.toPath())); } }
- 需在项目依赖中添加Google Cloud Storage客户端库(Maven示例):
<dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-storage</artifactId> <version>2.28.0</version> </dependency> - 确保应用运行在HotSpot系JVM(如OpenJDK、Oracle JDK)环境下。
四、关键注意事项
- 临时目录特性:Cloud Run的
/tmp目录仅在容器运行时可写,终止后会被清理,必须在优雅关闭窗口内完成上传; - 优雅关闭时长:若生成Heap Dump耗时较长(如大内存堆),需在Cloud Run服务配置中将优雅关闭超时调整至最长90秒;
- 镜像轻量化:使用
gsutil需安装gcloud SDK,会增加镜像体积,推荐优先使用Java客户端库或基于Alpine的轻量JDK镜像; - 上传效率:避免在关闭逻辑中添加冗余操作,防止因超时被强制终止。
内容的提问来源于stack exchange,提问作者Brian K
相关产品推荐
相关产品推荐

