JVM Crash的几个实例

本文记录了三起JVM Crash实例的排查过程,包括通过hs_err_pid.log文件定位潜在的JVM bug,调整-Xss参数解决jboss启动时的段错误问题,并提供了core.pid文件的分析方法。
部署运行你感兴趣的模型镜像

最近遇到几个JVM Crash的实例,一共三起,其中一起确定原因,也解决了,其它的还只能猜测可能的原因!

一、JVM 运行一段时间之后突然Crash,生成的hs_err_pid.log文件部分内容如下:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00002b42ca9e64f0, pid=14881, tid=1092757824
#
# JRE version: 6.0_18-b07
# Java VM: Java HotSpot(TM) 64-Bit Server VM (16.0-b13 mixed mode linux-amd64 )
# Problematic frame:
# V  [libjvm.so+0x5c04f0]
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

---------------  T H R E A D  ---------------

Current thread (0x00000000499ee800):  JavaThread "CompilerThread0" daemon [_thread_in_native, id=14905, stack(0x0000000041122000,0x0000000041223000)]

siginfo:si_signo=SIGSEGV: si_errno=0, si_code=1 (SEGV_MAPERR), si_addr=0x000000000000002c

Registers:
RAX=0x00002aab2ab21f40, RBX=0x0000000000000183, RCX=0x00002aab2a097e90, RDX=0x0000000000000008
RSP=0x000000004121e898, RBP=0x000000004121ead0, RSI=0x00002aab29f6d8c0, RDI=0x0000000000000000
R8 =0x0000000000001098, R9 =0x00002aab29373da0, R10=0x00002aab28eb06a0, R11=0x00002aab2ab21f70
R12=0x000000004121f798, R13=0x0000000000000052, R14=0x0000000000000000, R15=0x00002aab2a9e2ff0
RIP=0x00002b42ca9e64f0, EFL=0x0000000000010202, CSGSFS=0x000000000000e033, ERR=0x0000000000000004

通过google,推测可能是JVM的一个bug,但不能确定:http://www.oracle.com/technetwork/java/javase/crashes-137240.html#gbyzd

二、JVM运行一段时间之后突然Crash,生成的hs_err_pid.log文件如下:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00002b624490ac98, pid=2191, tid=1107622208
#
# JRE version: 6.0_18-b07
# Java VM: Java HotSpot(TM) 64-Bit Server VM (16.0-b13 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# V  [libjvm.so+0x5e9c98]
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

---------------  T H R E A D  ---------------

Current thread (0x000000004b8c4000):  GCTaskThread [stack: 0x0000000000000000,0x0000000000000000] [id=2197]

siginfo:si_signo=SIGSEGV: si_errno=0, si_code=128 (), si_addr=0x0000000000000000

Registers:
RAX=0x000000000d0e8cad, RBX=0x0000000789ddf538, RCX=0xf13bc057f13bc04c, RDX=0x0000000000000001
RSP=0x000000004204e820, RBP=0x000000004204e880, RSI=0x00002aaac0cf7120, RDI=0x000000004b8caf40
R8 =0xf13bc057f13bc04e, R9 =0x00000007d96af400, R10=0x0000000000000010, R11=0x0000000000000011
R12=0x0000000000000000, R13=0x000000000d0e8cad, R14=0x000000004b8caf40, R15=0xf13bc057f13bc04e
RIP=0x00002b624490ac98, EFL=0x0000000000010282, CSGSFS=0x0000000000000033, ERR=0x0000000000000000
  TRAPNO=0x000000000000000d
……
Heap
 par new generation   total 471872K, used 435920K [0x0000000770000000, 0x0000000790000000, 0x0000000790000000)
  eden space 419456K, 100% used [0x0000000770000000, 0x00000007899a0000, 0x00000007899a0000)
  from space 52416K,  31% used [0x000000078ccd0000, 0x000000078dce4000, 0x0000000790000000)
  to   space 52416K,  74% used [0x00000007899a0000, 0x000000078bfcff20, 0x000000078ccd0000)
 concurrent mark-sweep generation total 1572864K, used 1223031K [0x0000000790000000, 0x00000007f0000000, 0x00000007f0000000)
 concurrent-mark-sweep perm gen total 262144K, used 67047K [0x00000007f0000000, 0x0000000800000000, 0x0000000800000000)
通过google,推测可能是JVM的一个bug,但因为不能稳定重现,也不能确定: http://bugs.sun.com/view_bug.do?bug_id=6896647


三、jboss启动时,提示段错误,然后进程就crash了,在目录下生成了core.pid文件。

之前一直没有core.pid文件生成,折腾了比较久,启动时通过jmx连接来看,在crash之前的CPU和内存均看不出异常,而且crash了之后什么信息也没留下,因此一愁莫展!

后来有一位同学经过一阵努力,使得它可以在crash之前生成core.pid文件了,这个文件生成的head dump文件没看出异常,但是生成的堆栈信息里有一个堆栈长度特别长,联想到我们的-Xss=256k参数,以及之前由于这个参数出现过内存溢出现象。于是将这个参数改为1024k解决问题!具体原因见:

http://www.oracle.com/technetwork/java/javase/crashes-137240.html#gbyzz

对于core.pid文件,有几种方法可以分析:1.可以通过jmap生成heap dump文件;2.可以使用jstack查看crash时的堆栈信息;3.可以使用gdb查看crash时正在执行的代码等等。

您可能感兴趣的与本文相关的镜像

LobeChat

LobeChat

AI应用

LobeChat 是一个开源、高性能的聊天机器人框架。支持语音合成、多模态和可扩展插件系统。支持一键式免费部署私人ChatGPT/LLM 网络应用程序。

package com.walmart.aloha.payment.core.pulsar.producer; import com.walmart.aloha.payment.core.constants.PayConstants; import lombok.extern.slf4j.Slf4j; import org.apache.pulsar.client.api.*; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; /** * Pulsar生产者工厂类,用于创建和管理Pulsar生产者实例。 */ @Component @ConditionalOnProperty( value = {"pulsar.enable"}, matchIfMissing = true ) @Slf4j public class PulsarProducerFactory implements DisposableBean { @Autowired private PulsarClient pulsarProducerClient; private final Map<String, Producer<String>> producerAndTopicMapping = new ConcurrentHashMap<>(); private static final boolean ENABLE_BATCHING = true; private static final CompressionType COMPRESSION_TYPE = CompressionType.LZ4; private static final int BATCHING_MAX_PUBLISH_DELAY_MS = 10; private static final int SEND_TIMEOUT_SECONDS = 20; private static final int BATCHING_MAX_MESSAGES = 1000; private static final int MAX_PENDING_MESSAGES = 1000; private static final boolean BLOCK_IF_QUEUE_FULL = true; private static final int ROUND_ROBIN_ROUTER_BATCHING_PARTITION_SWITCH_FREQUENCY_MS = 10; private static final BatcherBuilder BATCHER_BUILDER = BatcherBuilder.DEFAULT; @Value(value = "${pulsar.tenant.name}") private String pulsarTenantName; @Value(value = "${pulsar.namespace.name}") private String pulsarNamespaceName; /** * 创建Pulsar生产者实例。 * * @param topicName 主题名称 * @return Pulsar生产者实例 */ private Producer<String> createProducer(String topicName) { try { String topicNamePrefix = new StringBuilder("persistent://") .append(pulsarTenantName) .append(PayConstants.PATH_SEPARATOR) .append(pulsarNamespaceName) .append(PayConstants.PATH_SEPARATOR).toString(); log.info("消息中心--topic:{} 创建生产者", topicNamePrefix + topicName); return pulsarProducerClient.newProducer(Schema.STRING) .topic(topicNamePrefix + topicName) .enableBatching(ENABLE_BATCHING) .compressionType(COMPRESSION_TYPE) .batchingMaxPublishDelay(BATCHING_MAX_PUBLISH_DELAY_MS, TimeUnit.MILLISECONDS) .sendTimeout(SEND_TIMEOUT_SECONDS, TimeUnit.SECONDS) .batchingMaxMessages(BATCHING_MAX_MESSAGES) .maxPendingMessages(MAX_PENDING_MESSAGES) .blockIfQueueFull(BLOCK_IF_QUEUE_FULL) .roundRobinRouterBatchingPartitionSwitchFrequency(ROUND_ROBIN_ROUTER_BATCHING_PARTITION_SWITCH_FREQUENCY_MS) .batcherBuilder(BATCHER_BUILDER) .create(); } catch (PulsarClientException e) { log.error("消息中心--topic:{} 创建生产者失败", topicName, e); return null; } } /** * 获取或创建Pulsar生产者实例。 * * @param topicName 主题名称 * @return Pulsar生产者实例 */ private Producer<String> getProducer(String topicName) { return producerAndTopicMapping.computeIfAbsent(topicName, this::createProducer); } /** * 发送消息到指定主题。 * * @param topicName 主题名称 * @param msg 消息内容 * @return 消息ID */ public MessageId sendMessage(String topicName, String msg) { if (topicName == null || msg == null) { throw new IllegalArgumentException("Topic and message cannot be null"); } Producer<String> producer = getProducer(topicName); if (producer == null) { log.error("消息中心--topic:{} 获取生产者失败", topicName); return null; } MessageId messageId = null; try { messageId = producer.send(msg); } catch (PulsarClientException e) { log.error("同步消息--topicName :{} 消息发送失败", topicName, e); } if (messageId != null) { log.info("同步消息:topicName:{}, message:{} 发送成功, messageId:{}", topicName, msg, messageId); } else { log.error("同步消息:topicName:{}, message:{} 发送失败", topicName, msg); } return messageId; } @Override public void destroy() { producerAndTopicMapping.forEach((topicName, producer) -> { try { producer.close(); } catch (PulsarClientException e) { log.error("消息中心--topic:{} 关闭生产者失败", topicName, e); } }); producerAndTopicMapping.clear(); } } 这段代码会导致老年代内存升高吗
最新发布
10-28
这段 Java 代码是基于 **Apache Pulsar** 的生产者工厂类,用于创建和管理多个 Pulsar 生产者(`Producer<String>`),并通过 `ConcurrentHashMap` 缓存它们。你问的是:**这段代码会导致老年代内存升高吗?** --- ### ✅ 回答问题 **有可能导致老年代内存升高,但通常不会直接“泄漏”或“无限增长”,是否引起老年代内存持续升高取决于使用方式、主题数量、对象生命周期和 GC 行为。** 我们逐项分析可能导致老年代内存升高的原因: --- ## 🔍 潜在导致老年代内存升高的因素 ### 1. `producerAndTopicMapping` 是永久缓存(无过期/清理机制) ```java private final Map<String, Producer<String>> producerAndTopicMapping = new ConcurrentHashMap<>(); ``` - 使用了 `computeIfAbsent(topicName, this::createProducer)` 来缓存每个 topic 对应的 `Producer`。 - **一旦创建,永远不会移除**(除非应用关闭时调用 `destroy()`)。 - 如果系统中频繁地使用不同的 `topicName`(比如动态生成大量唯一 topic 名称),那么这个 map 会不断增长。 > 🚨 **后果**: > - `Producer` 实例本身包含大量资源(Netty 连接、线程引用、配置对象等),占用堆内存较多。 > - 长时间存活的对象会被晋升到 **老年代(Old Generation)**。 > - 如果 `topicName` 数量很大且不重复,map 中的 entry 和对应的 `Producer` 实例将长期驻留老年代,造成老年代内存持续上升。 --- ### 2. `Producer` 实例较重,生命周期长 Pulsar 的 `Producer` 不是一个轻量级对象: - 它维护与 broker 的 TCP 连接; - 包含缓冲区、定时器、批处理队列; - 内部状态复杂,占用内存较大。 这类对象一旦被创建并长期持有(如放入静态 map),很容易进入老年代。 --- ### 3. `@Value` 注入字段拼接 topic 前缀(潜在风险较小) ```java String topicNamePrefix = "persistent://" + pulsarTenantName + "/" + pulsarNamespaceName + "/"; ``` 这部分只是字符串拼接,影响小,但如果 topicName 拼出来错误或者有歧义,可能导致无意中创建了过多不同名称的 topic,间接导致更多 Producer 被创建。 --- ### 4. `destroy()` 方法实现了关闭逻辑 ✅ ```java @Override public void destroy() { producerAndTopicMapping.forEach((topicName, producer) -> { try { producer.close(); } catch (PulsarClientException e) { log.error("...", e); } }); producerAndTopicMapping.clear(); } ``` ✅ 在 Spring 容器销毁时会调用此方法,释放所有资源。 ⚠️ 但是:如果应用没有正常关闭(例如 crash 或 kill -9),这些资源不会释放。 --- ### 5. 是否启用批量发送、压缩等不影响内存区域,但影响对象大小 虽然设置了如下参数: ```java .enableBatching(ENABLE_BATCHING) .compressionType(COMPRESSION_TYPE) .batchingMaxMessages(BATCHING_MAX_MESSAGES) ... ``` 这些主要影响消息发送效率和网络传输,并不会显著增加堆内对象数量。但若消息积压严重,`maxPendingMessages` 可能导致待发送消息堆积,进而增加内存压力。 不过这里发送的是 `String` 类型,且通过 `send(msg)` 同步阻塞发送,积压可能性较低。 --- ## ✅ 总结:是否会引发老年代内存升高? | 判断项 | 结论 | |-------|------| | 是否创建大量长期存活对象? | ✅ 是(Producer 实例) | | 是否缓存永不删除? | ✅ 是(Map 无清除策略) | | 是否可能因 topic 动态变化而膨胀? | ⚠️ 视业务而定 —— 若 topic 是固定的几个,则安全;若动态生成无数个,则危险 | | 是否最终释放? | ✅ 应用关闭时会释放(通过 `destroy()`) | | 是否容易 OOM 或 Full GC 频繁? | ⚠️ 如果 topic 数量多且不变,可能引发老年代内存升高 → Full GC 频繁 | 👉 **结论:** > **如果 topicName 是有限、固定的集合(比如只有 payment-event、refund-event 等几个),则不会导致老年代内存异常升高。** > > **但如果业务允许传入任意或动态生成的 topicName(例如带时间戳、UUID、用户ID),就会导致 Producer 实例无限增长,最终使老年代内存不断升高,甚至触发 Full GC 或 OOM。** --- ## 💡 建议优化方案 ### ✅ 方案一:限制 topic 缓存数量 + 添加 LRU 缓存淘汰机制(推荐) 使用 `Caffeine` 或 `Guava Cache` 替代 `ConcurrentHashMap`,自动控制缓存大小: ```xml <!-- Maven --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.8</version> </dependency> ``` ```java import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; // 替换原来的 ConcurrentHashMap private final Cache<String, Producer<String>> producerCache = Caffeine.newBuilder() .maximumSize(100) // 最多缓存 100 个生产者 .expireAfterAccess(30, TimeUnit.MINUTES) // 空闲 30 分钟自动关闭 .removalListener((String topic, Producer<String> producer, RemovalCause cause) -> { if (producer != null) { try { producer.close(); log.info("消息中心--topic:{} 已从缓存移除,原因: {}", topic, cause); } catch (PulsarClientException e) { log.error("关闭生产者失败", e); } } }) .build(); // 修改 getProducer 方法 private Producer<String> getProducer(String topicName) { return producerCache.get(topicName, this::createProducer); } ``` 这样可以防止无限缓存,避免老年代膨胀。 --- ### ✅ 方案二:校验 topicName 的合法性 & 白名单机制 在 `sendMessage()` 中加入校验: ```java Set<String> ALLOWED_TOPICS = Set.of("payment-event", "refund-event", "notification"); public MessageId sendMessage(String topicName, String msg) { if (!ALLOWED_TOPICS.contains(topicName)) { throw new IllegalArgumentException("Invalid topic name: " + topicName); } // ... } ``` 或者更灵活地使用正则匹配。 --- ### ✅ 方案三:监控老年代 GC 情况 建议开启 JVM 监控: ```bash -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log ``` 观察是否有频繁的 Full GC,配合 JConsole / VisualVM 查看老年代使用趋势。 --- ## ❓相关问题
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值