关于内存溢出,咱再聊点有意思的?

代码织云鹤
• 阅读 1446

概述

上篇文章讲了JVM在GC上的一个设计缺陷,揪出一个导致GC慢慢变长的JVM设计缺陷,可能有不少人还是没怎么看明白的,今天准备讲的大家应该都很容易看明白

本文其实很犹豫写不写,因为感觉没有太多值得探索的东西,不过文末估计会给你点小惊喜

或许大家曾经都碰到过HashMap因为其非线程安全的多线程并发操作导致cpu飙高的问题,不过这个问题在JDK8里已经解决掉了,其根本原因网上也早已遍地开花,所以我这篇文章里就不再熬述了,不了解的可以去网上找找相关文章,本文和大家聊的是看到的另外一个现象—-内存溢出

现象

同事丢了一个链接过来,是内存分析的,我看到一个线程占用的内存非常高,这个问题其实已然非常明显了,展开看了下线程栈
关于内存溢出,咱再聊点有意思的?

正在调用一个Map对象的toString方法,直到抛出java.lang.OutOfMemoryError,之所以这个栈顶能看到OutOfMemoryError的逻辑是因为配置了-XX:+HeapDumpOnOutOfMemoryError参数
不过这个参数只会生效一次,不会每次OOM的时候都做内存dump,大家可以想像一下,如果是代码的问题会发生连续的OOM,那连续做dump也没必要,于是JVM里控制这个参数只会在第一次发生OOM的时候做一次内存dump

分析

其实在我看到这个OutOfMemoryError栈之后,还没等同事说多少话,我就立马要同事去看我之前那篇关于OOM的文章了,想表达的是虽然这个线程栈里看到了OOM,但是内存泄露其实不一定是和这个线程有关的,可能只是临门一脚而已,不过后面细看了下这个线程占的内存其实真的挺高了,高达2G多,所以就这个案例来说还是和这个线程有关的,有时候不能太相信自己的经验,具体问题还是得具体分析才好

那为什么这个线程会占用这么大的内存呢?看到整个栈后面都在做字符串的拼接扩容动作,因为都是toString方法触发的,难道真的有个2G的字符串?询问同事他们说绝对不可能存在这么大的字符串,貌似老早之前有同事问过我类似的问题,不过我都一直怀疑他们说的,觉得肯定是存在这么大的字符串的,只是他们不知道而已,原来那个问题我也已经忘记最后情况了。今天又有类似的问题过来,我想也许我想的真的不对?后面同事打我电话说了下场景,他打印一个Map,但是这个Map其实是一个ConcurrentHashMap,是线程安全的,但是这个map里的value是一个HashSet,这个HashSet是非线程安全的,并且存在多个线程修改这个Set的情况,那会不会是因为并发导致的呢,HashSet里其实就是一个HasMap的结构,我觉得是很有可能的,于是要同事自己去模拟下这个场景,看能否重现出来

我继续看他们的内存dump,果然发现了一些猫腻,确实在打印那个HashSet过程中,next字段是循环连起来的,于是基本确定了死循环的存在,没过一会儿,同事也重现出来了,大概逻辑如下:

关于内存溢出,咱再聊点有意思的?

注意,这个得在JDK6或者7下跑才会重现,JDK8下不存在这个问题

Demo里就是两个线程同时对HashSet进行修改,可能带来的一个后果是里面的HashMap因为要扩容并且做rehash而出现死循环的情况,当有线程要打印这个HashSet的时候,会调用其toString方法,再看看其父类AbstractCollection的toString的逻辑:

关于内存溢出,咱再聊点有意思的?

就是挨个遍历,然后将值塞到StringBuilder里,如果正巧之前因为多线程的并发操作导致了死循环链的产生,那可能会导致这个StringBuilder会非常大,并且还会不断进行扩容,正如上面的堆栈看到的一样,这直接带来的一个后果就是出现内存溢出

内存富余下的OutOfMemory

对于同事线上碰到的那个问题看到的OOM提示是Requested array size exceeds VM limit,这个提示讲真我还是第一次碰到有发生的,假如说你的内存其实非常大,足够的剩余,但是当你要创建一个数组的时候,如果你的数组的长度超过Integer.MAX_VALUE-2的话,那你将会看到一个这个提示的OOM抛出来,其实这也是你能创建的数组的最大长度了,这或许很多人都没有注意到的,就把这个当做本文的一个最有价值的亮点吧

欢迎关注 PerfMa 社区,推荐阅读:
今天,进程告诉我线程它它它它不想活了
Java 虚拟机进程状态管理工具 jps 失效?吓尿了!
点赞
收藏
评论区
推荐文章
一次JVM GC长暂停的排查过程
在高并发下,Java程序的GC问题属于很典型的一类问题,带来的影响往往会被进一步放大。不管是「GC频率过快」还是「GC耗时太长」,由于GC期间都存在StopTheWorld问题,因此很容易导致服务超时,引发性能问题。
谈JVM参数GC线程数ParallelGCThreads合理性设置
作者:京东零售刘乐导读:本篇文章聚焦JVM参数GC线程数的合理配置,从ParallelGCThreads参数含义、参数设置,到参数实验以及修改意见进行解析。1.ParallelGCThreads参数含义在讲这个参数之前,先谈谈JVM垃圾回收(GC)算法的两
Stella981 Stella981
3年前
JVM(6):JVM 调优
JVM(6):JVM调优从Eclipse开始来源:纯洁的微笑,www.cnblogs.com/ityouknow/p/5647513.html概述什么是jvm调优呢?jvm调优就是根据gc日志分析jvm内存分配、回收的情况来调整各区域内存比例或者gc回收的策略;更深一层就是根据dump出来的内存结构和线程栈来
Stella981 Stella981
3年前
JVM调优
概述  什么是jvm调优呢?jvm调优就是根据gc日志分析jvm内存分配、回收的情况来调整各区域内存比例或者gc回收的策略;更深一层就是根据dump出来的内存结构和线程栈来分析代码中不合理的地方给予改进。eclipse优化主要涉及的是前者,通过gc日志来分析。本文主要是通过分析eclipsegc日志为例来示例如何根据gc日志来分析jvm内存而进
Stella981 Stella981
3年前
Kafka如何通过精妙的架构设计优化JVM GC问题
目录1、Kafka的客户端缓冲机制2、内存缓冲造成的频繁GC问题3、Kafka设计者实现的缓冲池机制4、总结一下“这篇文章,同样给大家聊一个硬核的技术知识,我们通过Kafka内核源码中的一些设计思想,来看你设计Kafka架构的技术大牛,是怎么优化JVM的GC问题的?1、Kafk
Stella981 Stella981
3年前
JVM系列【6】GC与调优1
JVM系列笔记目录虚拟机的基础概念class文件结构class文件加载过程jvm内存模型JVM常用指令GC与调优GC基础知识什么是垃圾​没有任何引用指向的一个对象或多个对象(循环引用)!file(https:
Stella981 Stella981
3年前
C++ MFC棋牌类小游戏day1
好用没用过C做一个完整一点的东西了,今天开始希望靠我这点微薄的技术来完成这个小游戏。我现在的水平应该算是菜鸟中的战斗鸡了,所以又很多东西在设计和技术方面肯定会有很大的缺陷,我做这个小游戏的目的单纯为了证明一下我到底还是不是个程序员。。。这个小游戏是我小时候玩过的一种棋盘类游戏,可能只在我们那边才会知道,不过现在小孩估计已经很少玩了,搞不好要失传了
Stella981 Stella981
3年前
JVM&NIO&HashMap简单问
_JVM&NIO&HashMap简单问_背景:前几天在网上看到关于JVM&NIO&HashMap的一些连环炮的面试题,整理下以备不时之需。_一、JVM_Java的虚拟机的面试内容主要包括GC、类加载机制和内存三大部分。如下是一个一个GC部分简单的连环炮:问:什么时候一个对象会被GC?答:当没有任何对象的引用指向该对
Wesley13 Wesley13
3年前
Java编程思想入门其实是一个坑!
20天之前入手,趁着开学比较闲,抓紧看英文版。之前上过斯坦福的cs106A做完了所有的作业,有一点点java的基础。开学三周看完了17章,基本都认真看了,难度超过3的练习也都做了。个人感觉写的比较杂乱,可能是因为我是新手的原因,很多时候给出的例子让人想不明白想表达什么。关于泛型(generics)的那一章感觉尤其杂乱,道理没有说清楚?自认为英文不错,应该
Stella981 Stella981
3年前
Python的垃圾回收机制
垃圾回收机制「垃圾回收(GC)」大家应该多多少少都了解过,什么是垃圾回收呢?垃圾回收GC的全拼是GarbageCollection,在维基百科的定义是:在计算机科学中,垃圾回收(英语:GarbageCollection,缩写为GC)是一种自动的内存管理机制。当一个电脑上的动态内存不再需要时,就应该予以释放,以让出内存,这种内存资源
一次JVM GC长暂停的排查过程
背景在高并发下,Java程序的GC问题属于很典型的一类问题,带来的影响往往会被进一步放大。不管是「GC频率过快」还是「GC耗时太长」,由于GC期间都存在StopTheWorld问题,因此很容易导致服务超时,引发性能问题。事情最初是线上某应用垃圾收集出现Fu