首要分享之前的一切文章 , 欢迎点赞收藏转发三连下次一定 >>>>
文章合集 : /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 从结果看过程 :运行反常的体系

以下是监控上截下来的一些图 :

一次极好的JVM日常实践分析

从这个图能够显着看到前后2个分段:

  • 老时代基本上占了运用总和
  • 年青代Survivor区和 年青代Eden区 简直为空

而随着重启,老时代清空,新生代基本上正常》》》


下面结合一个 GC 图来看详细的原因 :

一次极好的JVM日常实践分析

  • 首要 : 显着能够看到,老时代占用非常多,基本上等于悉数内存
  • 所以 : 当有新的目标创建的时分,因为老时代已经满了,无法接收更多的目标,年青代就无法被复制到老时代,从而频频的产生youngGC

而这种改变也能够经过下面的详细改变图能够看到>>>>>>

一次极好的JVM日常实践分析

以上是一个时间周期更短的图,包含了20个小时的分代状况,能够看到 :

  • 修正前youngGC非常频频,可是作用很差,基本上没什么改变
  • 修正后youngGC变少了,可是内存收回的作用反而更好了

定论

体系中存在反常,或许没有正确的装备JVM最大堆内 , 导致老时代一直增多而没有被正确的收回,导致年青代频频的产生youngGC.

根据这个思路,把时间周期拉长,就能够看到一个更清晰的改变曲线了 :

一次极好的JVM日常实践分析

三. 剖析问题的原因

所以,老时代越来越多,是问题的根源。其实定论也很容易得到,便是代码和装备的问题。

可是已然要学,就把中间的弯弯绕绕悉数理清楚。

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 下载后,履行剖析后不难发现端倪 :

一次极好的JVM日常实践分析

ConcurrentHashMap 和 地下的 ServiceImpl 占用空间都太大了,往里面翻一下代码就发现了问题 : HashMap 内的数据一直在插入,没有进行 remove 操作,而Map又还有运用,导致永久代一直增加。

3.3 那么正常的结果是什么样的

一次极好的JVM日常实践分析

首要,年青代会占大多数,永久代占一小部分,所以年青代的动摇和总理差不多。

一次极好的JVM日常实践分析

其次因为GC,就会使年青代一次次被收回一部分,形成动摇

一次极好的JVM日常实践分析

而随着老时代增加,则年青代和总量就会逐步分开,形成上下的结构。

总结

假如有相关的经历,其实这样的图像一眼就能看出问题,同时大约也能猜出原因。

上文的3种原因基本上在开始就猜想出来了,后面剖析Dump 也是在往那个方向猜想,最终得出的定论也差不多。

补充 :

常见引起内存溢出的原因

  • 分配的目标过大,超过了可用内存的约束。
  • 内存走漏导致无效目标占用了很多内存空间。
  • 程序中存在死循环或递归调用,导致内存耗费过快。
  • 虚拟机参数装备不妥,导致内存缺乏