首要分享之前的一切文章 , 欢迎点赞收藏转发三连下次一定 >>>>
文章合集 : /post/694164…
Github : github.com/black-ant
CASE 备份 : gitee.com/antblack/ca…
一. 前语
JVM 的概率背了不少,可是对于一个体系来说,出现JVM问题通常都是前期刚搭建时,或许代码写错了导致各种问题的出现。
一旦代码写的稳了,体系成熟了,反而很少碰到这类问题,陆陆续续也忘了不少。
这一次归于修正装备后发现JVM改变比较显着,想一想后也大约了解了原因,可是却是一次可贵的机会去巩固一下各种概念。
二. 重新了解分代
2.1 概念温习
要做一个简单的JVM 剖析,咱们只需要知道这些概念 :
堆内存是什么
- 堆内存用于存储目标实例和数组
- 堆内存在程序运行时动态分配和扩展,可是能够经过 Xms 和 Xmx 进行初始分配
- 废物收回会对堆内存的目标进行管理
堆内存的废物收回
- 当JVM中的堆内存空间缺乏时,可能会触发废物收回
- 当某个分代的内存缺乏时,也可能触发废物收回
- 当体系处于闲暇状态时,JVM可能会挑选在此时触发废物收回
并行和并发的差异
- 并行 : 利用多个线程并行地履行废物收回操作,并行废物收回需要暂停运用程序的履行,因为它需要对整个堆进行扫描和处理。 并行的废物收回器吞吐更快
- 并发 : 在运用程序运行的同时进行废物收回,能够在不中止运用程序的状况下,与运用程序并发地履行能够在不中止运用程序的状况下,与运用程序并发地履行
并发的目标是尽量减少运用程序的中止时间,以提高体系的呼应功能、
FullGC 和 YoungGC 的差异
// Young GC
- 针对Java堆中的年青代进行的废物收回操作
// Full GC
- 对整个Java堆,包括年青代和老时代,进行的废物收回操作
- 耗时长,需要暂停运用的履行,会导致较长的中止时间
2.2 从结果看过程 :运行反常的体系
以下是监控上截下来的一些图 :
从这个图能够显着看到前后2个分段:
- 老时代基本上占了运用总和
- 年青代Survivor区和 年青代Eden区 简直为空
而随着重启,老时代清空,新生代基本上正常》》》
下面结合一个 GC 图来看详细的原因 :
- 首要 : 显着能够看到,老时代占用非常多,基本上等于悉数内存
- 所以 : 当有新的目标创建的时分,因为老时代已经满了,无法接收更多的目标,年青代就无法被复制到老时代,从而频频的产生youngGC
而这种改变也能够经过下面的详细改变图能够看到>>>>>>
以上是一个时间周期更短的图,包含了20个小时的分代状况,能够看到 :
- 修正前youngGC非常频频,可是作用很差,基本上没什么改变
- 修正后youngGC变少了,可是内存收回的作用反而更好了
定论
体系中存在反常,或许没有正确的装备JVM最大堆内 , 导致老时代一直增多而没有被正确的收回,导致年青代频频的产生youngGC.
根据这个思路,把时间周期拉长,就能够看到一个更清晰的改变曲线了 :
三. 剖析问题的原因
所以,老时代越来越多,是问题的根源。其实定论也很容易得到,便是代码和装备的问题。
可是已然要学,就把中间的弯弯绕绕悉数理清楚。
3.1 概念储藏
问题一 : 老时代什么环节被收回?
- 老时代是在FullGC环节或许老时代内存空间缺乏时被收回的
- FullGC 会对整个内存进行收回,包括年青和老时代
- 老时代通常采用符号-清除(Mark-Sweep)或符号-整理(Mark-Compact)的废物收回算法
问题二 :那么老时代为什么不被收回?
- 目标依然被引证 :被其他代码引证的目标是不能被废物收回给正确收回的,相似的仍是长生命周期的目标
- 未满意GC战略 : 假如条件未满意或战略未触发,老时代也不会被收回
问题三 : 老时代要是满了会怎样
- 内存走漏 : 不同于内存溢出,内存走漏指无效目标依然被引证,终究导致内存溢出
- 内存溢出 : OutOfMemoryError ,新生代和老时代都无法继续分配内存
- 体系功能下降 : 体系功能下降的根源是假如内存满了,会频频的进行 FullGC , Stop the world 每次都会导致体系暂停,同时影响体系的吞吐量
3.2 再看问题
- S1 : 能够看到,fullGC 很少产生 = 说明还没到内存鸿沟,并没有触发 fullGC
- S2 : 老时代逐步变多 = 说明代码中确实存在问题
- S3 : 当老时代占用较少时,新生代的动摇非常大 = 说明装备有问题,没有为运用装备合理的内存空间
根据上述得到的三个定论,开始对代码和装备进行梳理
上道具 : Arthas 好东西
/post/719358…
用 Arthas Dump 下载后,履行剖析后不难发现端倪 :
ConcurrentHashMap 和 地下的 ServiceImpl 占用空间都太大了,往里面翻一下代码就发现了问题 : HashMap 内的数据一直在插入,没有进行 remove 操作,而Map又还有运用,导致永久代一直增加。
3.3 那么正常的结果是什么样的
首要,年青代会占大多数,永久代占一小部分,所以年青代的动摇和总理差不多。
其次因为GC,就会使年青代一次次被收回一部分,形成动摇
而随着老时代增加,则年青代和总量就会逐步分开,形成上下的结构。
总结
假如有相关的经历,其实这样的图像一眼就能看出问题,同时大约也能猜出原因。
上文的3种原因基本上在开始就猜想出来了,后面剖析Dump 也是在往那个方向猜想,最终得出的定论也差不多。
补充 :
常见引起内存溢出的原因
- 分配的目标过大,超过了可用内存的约束。
- 内存走漏导致无效目标占用了很多内存空间。
- 程序中存在死循环或递归调用,导致内存耗费过快。
- 虚拟机参数装备不妥,导致内存缺乏