You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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):
    jmap -dump:format=b,file=/tmp/heapdump.hprof 1
    
    Cloud Run容器内主进程PID通常为1,直接用1即可。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 06:25:18