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

概述

上篇文章讲了JVM在GC上的一个设计缺陷,揪出一个致使GC慢慢变长的JVM设计缺陷,可能有很多人仍是没怎么看明白的,今天准备讲的你们应该都很容易看明白java

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

或许你们曾经都碰到过HashMap由于其非线程安全的多线程并发操做致使cpu飙高的问题,不过这个问题在JDK8里已经解决掉了,其根本缘由网上也早已遍地开花,因此我这篇文章里就再也不熬述了,不了解的能够去网上找找相关文章,本文和你们聊的是看到的另一个现象—-内存溢出安全

现象

同事丢了一个连接过来,是内存分析的,我看到一个线程占用的内存很是高,这个问题其实已然很是明显了,展开看了下线程栈image.png多线程

正在调用一个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的结构,我以为是颇有可能的,因而要同事本身去模拟下这个场景,看可否重现出来ui

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

image.png

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

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

image.png

就是挨个遍历,而后将值塞到StringBuilder里,若是正巧以前由于多线程的并发操做致使了死循环链的产生,那可能会致使这个StringBuilder会很是大,而且还会不断进行扩容,正如上面的堆栈看到的同样,这直接带来的一个后果就是出现内存溢出

内存富余下的OutOfMemory

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

欢迎关注 PerfMa 社区,推荐阅读:
JVM Code Cache空间不足,致使服务性能变慢
海量链接服务端CMS调优记

相关文章
相关标签/搜索