当前位置:首页 > 每日看点 > 正文内容

内存泄漏和内存溢出有啥区别?

卡卷网1年前 (2024-12-15)每日看点248

昨天线上环境崩了

java堆内存溢出。。。

报错:java.lang.OutOfMemoryError: Java heap space

下面我将我排查问题的思路和过程记录了下来

1. 场景

  1. 客户端跟Java服务端通过websocket连接建立长链接并发送语音数据(text格式)
  2. Java服务端跟听写引擎建立长链接并发送语音数据
  3. 听写引擎将实时转写的内容返回给Java服务端,Java服务端再将数据处理返回给客户端

2. 排查过程

2.1 通过命令查看异常进程

由于是堆内存溢出异常,所以我先检查了一下服务器的负载和堆内存试用情况

通过 top 命令查看服务器进程情况,发现pid为1的一个java进程cpu使用率达到90%多

内存泄漏和内存溢出有啥区别?  第1张

这个进程正好是出现异常的这个java应用

通过 jps 命令也可以查看正在运行的java应用

内存泄漏和内存溢出有啥区别?  第2张


2.2 分析堆内存使用情况

接下来查看这个应用的堆内存使用情况 通过 jmap -heap pid 这个命令

内存泄漏和内存溢出有啥区别?  第3张

简要的对上图 中的内存使用情况做简单的说明:

MinHeapFreeRatio = 40

说明:当堆内存的空闲比例低于这个值时,JVM 会尝试增加堆大小。

影响:设置为 40 表示当堆内存的空闲部分小于 40% 时,JVM 会考虑增加堆大小。

MaxHeapFreeRatio = 70

说明:当堆内存的空闲比例高于这个值时,JVM 会尝试减少堆大小。

影响:设置为 70 表示当堆内存的空闲部分大于 70% 时,JVM 会考虑减少堆大小。

MaxHeapSize = 536,870,912 (512.0 MB)

说明:JVM 最大堆内存大小。

影响:这是 JVM 可以使用的最大内存,当前设置为 512 MB。如果应用程序需要更多内存,可能会遇到 OutOfMemoryError。

NewSize = 11,141,120 (10.625 MB)

说明:新生代(Young Generation)的初始大小。

影响:这是新生代的最小容量,用于存储新创建的对象。

MaxNewSize = 178,913,280 (170.625 MB)

说明:新生代的最大容量。

影响:这是新生代可以增长到的最大大小。

OldSize = 22,413,312 (21.375 MB)

说明:老年代(Old Generation)的初始大小。

影响:这是老年代的最小容量,用于存储晋升对象。

NewRatio = 2

说明:老年代与新生代的比例。例如,NewRatio=2 意味着老年代是新生代的两倍大。

影响:调整新生代和老年代的比例,影响垃圾回收的频率和效率。

SurvivorRatio = 8

说明:Eden 区与 Survivor 区的比例。例如,SurvivorRatio=8 意味着 Eden 区是每个 Survivor 区的八倍大。

影响:调整 Eden 和 Survivor 区的大小,影响对象在年轻代中的存活时间。

MetaspaceSize = 21,807,104 (20.796875 MB)

说明:Metaspace 的初始大小,用于存储类元数据。

影响:Metaspace 的初始分配大小,影响类加载的速度。

CompressedClassSpaceSize = 1,073,741,824 (1024.0 MB)

说明:压缩类空间的大小,用于存储类的内部表示。

影响:限制类元数据的存储空间。

MaxMetaspaceSize = 17,592,186,044,415 MB

说明:Metaspace 的最大大小。实际上,这个值非常大,几乎等于无限制。

影响:理论上没有限制,但实际受限于物理内存和操作系统。

G1HeapRegionSize = 0 (0.0MB)

说明:G1 垃圾收集器的区域大小。这里显示为 0,意味着未使用 G1 收集器。

影响:确认当前使用的不是 G1 收集器。


简单分析: 可以很明显的看到新生代和老年代几乎完全被使用(99.99% 和 100%),这意味着几乎没有可用的堆内存

如果此时出现Java heap space很大可能是因为内存使用完 然后又有对象被分配,这个时候没有空间并且在执行垃圾回收的时候没有回收到对象 所以会溢出

可以通过 jstat -gc <pid> 命令查看垃圾回收信息(由于我当时没有执行 这里就不放截图了)

如果内存一直被占用没有被回收 大概率就是有对象或者数据一直被占用无法执行回收(这里是我们排查问题的关键)

2.3 借助工具分析

由于生产环境不方便排查问题,所以这里借助VisualVM工具

通过如下命令导出快照通过 VisualVM 进行分析

jmap -dump:format=b,file=/tmp/sdc.hprof pid


内存泄漏和内存溢出有啥区别?  第4张


VisualVM 工具就不多介绍了,在jdk的bin目录中就可以启动

将我们从服务器上导出的 .hprof 文件导入到VisualVM中进行分析

导入 -- > 选择文件

内存泄漏和内存溢出有啥区别?  第5张


找到Object类页面

发现char[] 占用极高

内存泄漏和内存溢出有啥区别?  第6张


点开发现里面存着大量的文本信息,这些信息正是客户端通过参数传过来的数据,这很不正常,一般来说客户端传递的参数不属于成员变量或者对象,数据会随着线程或者方法的执行完毕而销毁

内存泄漏和内存溢出有啥区别?  第7张


感觉问题应该是出现在这里,我们在点开一条数据查看 ps:一般堆内存溢出这个异常都是由于对象重复创建或者数组存储数据太大导致的,而我这次查看没有发现有重复创建对象或者大对象,总之,一开始一头雾水

发现确实有对象在引用着他,所以无法被回收

内存泄漏和内存溢出有啥区别?  第8张


2.4 本地复现问题

为了验证并且对比问题,我在本地复现了下生产环境的异常

为了快速模拟堆内存溢出,我将我本地程序堆内存故意调小,借助idea工具将堆内存调整为 60 M

内存泄漏和内存溢出有啥区别?  第9张


第二是借助jmeter工具进行压测,由于我的接口是websocket接口,所以还需要借助一个插件 JMeterWebSocketSamplers-1.2.9.jar (大家可以在网上搜一下,如果不能下载可以找我获取, 公主号回复: jw) 将插件放到jmeter的安装目录 /lib/ext/ 下

jmeter的试用这里就不过多介绍了,不熟悉的可以自行查阅一下

我这里设置了 1 个线程,间隔 3 s,一直循环

内存泄漏和内存溢出有啥区别?  第10张


通过我们的VisualVM进行监控

内存泄漏和内存溢出有啥区别?  第11张


压测后观察服务和VisualVM还有压测工具

通过察看结果树发现 17 次之后就异常了

内存泄漏和内存溢出有啥区别?  第12张

服务异常截图:

内存泄漏和内存溢出有啥区别?  第13张


观察压测工具: 可以看到50Mb的时候就溢出了(这里大家可能要留意下,后面分析应该还会用到)

内存泄漏和内存溢出有啥区别?  第14张


点击Heap Dump 生成快照文件 发现和生产环境是同样的问题

内存泄漏和内存溢出有啥区别?  第15张


3. 问题解决过程

接下来就开始排查为啥前端传进来的数据会被一直占用

ps:一开始我为了省事使用http接口压测了一下传输大数据,眼看就把cpu干冒烟了 也没压出异常来

那肯定就是websocket的锅 !!!

3.1 websocket缓冲区分析

上面查看分析快照的时候 也发现了被websocket的类HeapCharBuffer引用

内存泄漏和内存溢出有啥区别?  第16张


那 HeapCharBuffer 中 messageBufferText 到底是干啥的呢?

==messageBufferText== 通常是指用于存储接收到的文本消息的缓冲区。当通过 WebSocket 接受到文本消息时,这些消息会暂时存储在这个缓冲区中,直到被应用程序读取。

说到 messageBufferText 那就不得不提到 maxTextMessageBufferSize

==maxTextMessageBufferSize== 定义了上述缓冲区的最大容量,即可以存储的最大文本消息量或大小。这个值设定了接收缓冲区的界限,防止由于过多未处理的消息导致内存耗尽

这里需要注意⚠️一点,也是引起我们问题的所在:

就是每次建立一个websocket连接都会创建一个最大的缓冲区 maxTextMessageBufferSize

那 maxTextMessageBufferSize 的大小是如何定义的呢,通过websocket的配置类来定义 我之前的定义是 512 k

@Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); // 在此处设置bufferSize container.setMaxTextMessageBufferSize(512000); container.setMaxBinaryMessageBufferSize(512000); container.setMaxSessionIdleTimeout(60 * 1000L); // 60s 超时时间 return container; }

通过上面的分析,可以得出以下结论:

  1. 缓冲区设置的内存偏大导致建立大量连接的时候占用大量内存,而实际每次建立连接的时候缓冲区可能用不了512k
  2. 连接结束之后缓冲区没有被清空

还记得我们上面压测的数据吗,17次之后就异常了,按照我们的结论每次建立连接消耗512k内存,那17次就是8704k 大约8.5mb,我们的堆初始的使用内存大约为40mb多,然后出现溢出的时候使用内存是大约50mb,那正好就是加上我们的8.5mb出现的异常,这里只是一个估算,肯定还有其他地方有内存消耗

为了验证我又将 maxTextMessageBufferSize 的大小调整为了 20k 然后压测 发现300多次才出现异常,那肯定就是因为maxTextMessageBufferSize引起的

但是调整maxTextMessageBufferSize的大小还不能从根本上解决问题,还要找到问题的根源,那就是为啥连接关闭后缓冲区没有被回收。

3.2 GC Root 引用链分析

堆内存都满了,但是数据都没有被垃圾回收只能说明一个问题,就是你的对象还被别人引用着。

接下来我们就通过查看messageBufferText的引用链来寻找问题的根源

我直接截图吧,我们找到有问题的数据(这个在上面使用工具的时候已经讲过了),一开始我们没有往深了挖这个链路,现在通过GC Root这个工具将链路展开

可以很清晰的看到我们的messageBufferText 的大概引用流程: messageBufferText --> ConcurrentHashMap --> ClassLoader(这个是根级) messageBufferText一直在被ConcurrentHashMap引用着 无法被释放


内存泄漏和内存溢出有啥区别?  第17张


最后我排查了下我的代码,连接每次都会正常关闭,问题就出在 ConcurrentHashMap 上,我使用 ConcurrentHashMap 存储了客户端实例,在存储的时候使用 seesionId 作为key,然后加了前缀。。

大概是这样:

内存泄漏和内存溢出有啥区别?  第18张


但是在清除client实例的时候,大概是这样的(当时估计犯神经了):

内存泄漏和内存溢出有啥区别?  第19张


神龙见首不见尾 !!!

虽然是由一个不起眼的小问题引起的,但是整个堆内存溢出的排查思路还是值得我们复盘和学习的。

One more thing

原来时间真的不是一条横跨在你面前的河,有着此岸和彼岸,而是一条挂在悬崖上的瀑布,奔流直下,一去无回。

有兴趣一起学习,加入编程小组的同学可以点击

Java内存泄露生产环境完整排查思路

扫描二维码推送至手机访问。

版权声明:本文由卡卷网发布,如需转载请注明出处。

本文链接:https://www.kajuan.net/ttnews/2024/12/4582.html

分享给朋友:

相关文章

创业:集思广益并完善您的商业理念

用史蒂夫乔布斯的话来说,“做伟大工作的唯一方法就是热爱你所做的事情。开始自己的事业是迈向自己喜欢的工作的一步。但是,从形成想法到创建商业网站,在您深入研究之前,需要考虑几个基本步骤和问题:您要解决什么问题?您的目标受众是谁?您的产品或服务与...

推荐几个问卷调查平台?

推荐几个问卷调查平台?

我给大家免费推荐一些市面上不多,且稳定的免费的问卷平台,这个在网上都是能够搜索到的,有的还是世界500钱企业,这里推荐的基本上都是上市的问卷公司了。上面都是可以免费去注册的,对外公开开放的,做完了直接奖励美刀的,不需要兑换卡什么的。Cint...

感觉手机配置都差不多,为什么有的手机能卖2k-3k,而有的手机却能卖到6k-8k?

感觉手机配置都差不多,为什么有的手机能卖2k-3k,而有的手机却能卖到6k-8k?

与所有的商品一样,手机的价格,也是由它的成本所决定的。虽然看起来3000元的手机和6000的手机配置差不多,甚至处理器都可能是同一个,但在很多大家容易忽略的地方,决定了两者价格的不同:例如手机的外观,塑料的机身,与素皮机身和玻璃机身就完全不...

大量刷短视频,会让大脑变笨拙吗?

会。我曾经是一名高三学生,亲身实践过。当时集中突破语文,每天都在刷语文卷。然后有一天想躺一下刷手机,结果短视频刷完了,我再去看哪些文章,只觉头晕眼花,难以理解文字。不过好在这种情况是短时间的,过了一天我的能力又恢复了。在我看来,长期刷短视频...

为什么我感受不到 1500 元的手机比四五千的差?

我长期使用两千元左右的安卓机,一天接近8小时的重度手机使用者。某天我突然想试试看传说中非常赛艇的苹果。狠了心,砸了钱。七千大样买了爱疯。就这?什么辣鸡玩意。而且因为我一直更新软件,用了两年爱疯就卡了。并没有传说中的用四五年不卡。用了这么一次...

NAS那么好,为什么还是没能成为大多数家庭必备的存储设备?

NAS那么好,为什么还是没能成为大多数家庭必备的存储设备?

最主要原因是因为——贵!看看我家里搭建的这一套吧。目前我家中有5台常用的NAS,分别为群晖DS1522+、威联通TS-464C2、绿联DX4600 Pro 、极空间Z4S、威联通TS-AI642。个人认为,这其中的每台NAS都是时代的翘楚,...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。