经验整理-11-JVM-3-垃圾回收机制算法-jdk1.8-实战总结

总结:

案例1:

一.jvm分为年轻代,年老代,持久代
1.年轻代:年轻代主要存放新创建的对象(Eden 区,垃圾回收会比较频繁。(稍微讲细一点就是即可,年轻代分成Eden Space和Suvivor Space。当对象在堆创建时,将进入年轻代的Eden Space。垃圾回收器进行垃圾回收时,扫描Eden Space,如果对象仍然存活,则复制到Suvivor Space。)

2.年老代:年老代主要存放JVM认为生命周期比较长的对象(在扫描Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个持久化对象,则将其移到Old Gen。)

3.持久代:持久代主要存放类定义、字节码和常量等很少会变更的信息

二.引出gc算法
年轻代使用的是复制算法gc频繁,使用复制算法才能达到这种效果:内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可。避免频繁创建对象导致碎片过多,一般会对算法优化来规避算法占用内存的问题,优化后有效内存能近乎达到百分之90,估计也不会问那么多,点到为止)

年老代使用的标记-整理算法(因为较少的发生gc,使用标记整理算法提高内存利用率,关键是最后整理那一步存活对象移动前一端,就把内存碎片问题解决了,提高内存利用率

直观的对比:
效率:复制算法>标记-整理算法
内存整齐度(碎片效果=0):复制算法=标记-整理算法
内存利用率:标记-整理算法>复制算法

三.如何实施调优
jvm参数设置,根据机器性能为程序运行分配合理区大小

四.善后工作
使用jdk自带的jvisualvm,jconsole等工具监测程序是否发生线程阻塞,内存泄漏,以及观察gc频率是否存在异常等

案例2:

一、了解jvm启动流程:
è¿éåå¾çæè¿°
备注:

è¿éåå¾çæè¿°
Heap(堆内存)=eden+2survivor(年轻代)+ParOldGen(老生代)+Perm(jdk8以前)。
jdk8以后将永久代替换为MetaSpace(元空间)存在于本地内存。
from survivor 和 to survivor大小相同,且保证一个为empty.

二、实战演练
1、JVM GC执行日志:
è¿éåå¾çæè¿°

gc解析日志:我们可以看到 由于年轻代的eden区100%,触发了红色框说由于内存分配失败,所以gc回收,eden区gc后剩余60567k(看年轻代蓝色变化)存放于年轻代的survivor(看绿色survivor增长69%)。
红色框解读:
[PSYoungGen: 524800K->60567K(611840K)] 。格式为[PSYoungGen: a->b©].
PSYoungGen,表示新生代使用的是多线程垃圾收集器(并行回收集器Parallel Scavenge。a为GC前,新生代已占用空间,b为GC后,新生代已占用空间。新生代又细分为一个Eden区和两个Survivor区,Minor GC之后Eden区为空,b就是Survivor中已被占用的空间。括号里的c表示整个年轻代的大小。
524800K->60647K(2010112K),格式为x->y(z)。x表示GC前堆的已占用空间,y表示GC后堆已占用空间,z表示堆的总大小。
由新生代和Java堆占用大小可以算出年老代占用空间,此例中就是2010112K-611840K=1398272k。
[Times: user=0.46 sys=0.02, real=0.13 secs] 提供cpu使用及时间消耗,user是用户态消耗的cpu时间,sys是系统态消耗的cpu时间,real是实际的消耗时间。
2、JVM gc执行过程:
è¿éåå¾çæè¿°

示例1,拉圾回收过程原理:(执行过程如上图根据执行日志顺序)
第一步,新生代内存分配区域eden空间100%满后,无法分配内存。
第二步,gc开始回收、保留一部分剩余存活对象存放survivor(假设to区),利用copy复制算法,把from的剩余存活对象转到to区,始终保持一个(from)survivor为empty。并为转移对象age+1.
第三步,之间一直循环上述步骤,当age满足很大的时候触发老年代gc回收保存到old 老年代区。(老年这里还不是full gc,还没满)
第四步,由于不断会有对象进入老年代老年代内存会一直增大加上新生代达到临界值内存最大值触发full gc进行整体回收。

疑问点:那你没有说明元空间啊?元空间干啥的呢?
元空间存放:class文件、静态对象、属性等。而且在永久代的时候默认大小256m。但是在元空间jdk8后,它是jvm根据需要动态加载大小(可能是上限是全部剩余内存,可拓展,节省了内存空间)

三、实战后记忆:
网上一个很形象的例子描述对象在JVM堆内存中的生命周期:

我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“To”区,自从去了Survivor区,我就开始漂泊了,因为Survivor的两个区总是交换名字,所以我总是搬家,搬到To Survivor居住,搬来搬去,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。

四、JVM性能调优建议:

jvm调优没有一个固定模板配置说必须如何操作,它需要根据系统的情况不同对待。

但是可以有如下建议:

1、初始化内存和最大内存尽量保持一致,避免内存不够用继续扩充内存最大内存不要超过物理内存,例如内存8g,你可以设置最大内存4g/6g但是不能超过8g否则加载类的时候没有空间会报错

2、gc/full gc频率不要太高每次gc时间不要太长、根据系统应用来定。(尤其是要避免频繁full gc

五、算法描述:

引用计数(Reference Counting):

比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。

标记-清除(Mark-Sweep):
è¿éåå¾çæè¿°

此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片

复制(Copying):

è¿éåå¾çæè¿°
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。

标记-整理(Mark-Compact):
è¿éåå¾çæè¿°

算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题

六、本篇文章不足点:

此篇文章参数只设置了初始化内存和最大内存其余都是系统自动分配,接下来会不断进行优化配置,面试掌握上面的内容基本上就可以了。

如果需要深入了解想要加入BATJ/TMD的,那么还需要点击下面深入了解下。

案例3:JVM8以上G1算法步骤图解

è¿éåå¾çæè¿°

来自官方解释:
è¿éåå¾çæè¿°

从逻辑上讲,G1是世代的。一组空白区域被指定为合乎逻辑的年轻一代。在图中,年轻一代是淡蓝色。分配工作是由那些合乎逻辑的年轻一代完成的,当年轻一代充满时,这些地区就是垃圾收集(一个年轻的收藏)。在某些情况下,在一组年轻地区之外的地区(深蓝色的旧地区)可以同时进行垃圾收集。这被称为混合收藏。在该图中,收集的区域用红色框标记。该图显示了混合收藏,因为收集了年轻区域和旧区域。垃圾收集是一个压缩集合,它将活动对象复制到选定的初始空白区域。根据幸存对象的年龄,可以将对象复制到幸存者区域(标有“S”)或旧区域(未具体显示)。标有“H”的地区含有大于半数地区的特殊物体,请参见了Humongous对象和分配了Humongous在垃圾-First垃圾收集。

G1 GC模式
G1提供了两种GC模式,Young GC和Mixed GC,两种都是完全Stop The World的。

Young GC:选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。
Mixed GC:选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。
由上面的描述可知,Mixed GC不是full GC,它只能回收部分老年代的Region,如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。所以我们可以知道,G1是不提供full GC的。

上文中,多次提到了global concurrent marking,它的执行过程类似CMS,但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程,G1 GC分为四个步骤:

初始标记(initial mark,STW)。它标记GC Root可达的对象
并发标记(Concurrent Marking)。这个阶段从GC Root开始对heap中的对象标记,标记线程与应用程序线程并行执行,并且标记各个Region的存活对象
最终标记(Remark,STW)。
修正在并发标记阶段发生变化的对象,(标记最终哪些)将被回收
清除垃圾(Cleanup)。清除空Region(没有存活对象的),加入到free list。(可能受配置时间影响,不全回收
筛选回收价值高的和成本低的
第一阶段initial mark是共用了Young GC的暂停,这是因为他们可以复用root scan操作,所以可以说global concurrent marking是伴随Young GC而发生的。第四阶段Cleanup只是回收了没有存活对象的Region,所以它并不需要STW。

Young GC发生的时机大家都知道,那什么时候发生Mixed GC呢?其实是由一些参数控制着的,另外也控制着哪些老年代Region会被选入CSet。

G1HeapWastePercent:在global concurrent marking结束之后,我们可以知道old generation region中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC。
G1MixedGCLiveThresholdPercent:old generation region中的存活对象的占比,只有在此参数之下,才会被选入CSet。
G1MixedGCCountTarget:一次global concurrent marking之后,最多执行Mixed GC的次数。
G1OldCSetRegionThresholdPercent:一次Mixed GC中能被选入CSet的最多old generation region数量。
除了以上的参数,G1 GC相关的其他主要的参数有:

参数    含义
-XX:G1HeapRegionSize=n    设置Region大小,并非最终值
-XX:MaxGCPauseMillis    设置G1收集过程目标时间,默认值200ms,不是硬性条件
-XX:G1NewSizePercent    新生代最小值,默认值5%
-XX:G1MaxNewSizePercent    新生代最大值,默认值60%
-XX:ParallelGCThreads    STW期间,并行GC线程数
-XX:ConcGCThreads=n    并发标记阶段,并行执行的线程数
-XX:InitiatingHeapOccupancyPercent    设置触发标记周期的 Java 堆占用率阈值。默认值是45%。这里的java堆占比指的是non_young_capacity_bytes,包括old+humongous

案例4:

https://blog.csdn.net/wolf_love666/article/details/85073504

零、总结:
本次问题通过分析,由于平时70%+的内存使用率,目前达到88%是由于5个月系统未重新发布内存数据和缓存不断增加以及堆内存的增加累计达到了内存使用率的报警阀值88%。


那么平时如果出现内存使用率偏高的问题,应该如何解决呢?下面的几个步骤其实就是从硬件-》系统-》进程来由大到小解决的。
1、由于内存分配问题(也就是我这里的问题,对应解决办法如步骤1和2),
2、长期持有super big对象耗内存(对应解决办法如步骤3)
3、死锁问题(对应解决办法如步骤4)
4、其他原因比如poll长连接或者其他导致并发线程增多的原因(对应解决办法如步骤5和6)
5、定位某个进程的内存什么问题(如步骤7)
6、线程具体什么代码或者什么原因导致的(如步骤8)

对于jvm8+调优点击下面
参考实战和指导手册

如果你是小白码农,还没有到达码工的层级,那么可以按照如下的教程定位问题。如果依然有疑问可以关注公众号【小诚信驿站】或者加 QQ群300458205

一、知识点了解:
了解下硬件和系统和进程之间的关系。

1.1硬件:
top执行命令可以得到

Cpu(s):  0.0%us,  0.3%sy,  0.0%ni, 99.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:     73728k total,    70048k used,     3680k free,        0k buffers
Swap:    16384k total,     4696k used,    11688k free,    64716k cached
1
2
3
top看到的内存使用情况,有一部分是缓存,mem那行后面有个buffers ,swap那行后面有个cached,这两个就是缓存大小。所以如果要计算应用程序真正使用物理内存的情况,应该是used-cached-buffers才对,所以刚才top看到的物理内存使用情况为70048k-64716k=5332k。
1
如果想要直接看内存使用情况可以执行 free命令 后面加个m是以M为单位显示

free -m
-----------
              total       used       free     shared    buffers     cached
Mem:            72         69          2          0          0         63
-/+ buffers/cache:          5         66
Swap:           16          4         11
1
其中第一行用全局角度描述系统使用的内存状况: 
total——总物理内存 
used——已使用内存,一般情况这个值会比较大,因为这个值包括了cache+应用程序使用的内存 
free——完全未被使用的内存 
shared——应用程序共享内存 多个进程之间共享的内存部分,比如公共库libc.so等
buffers——缓存,主要用于目录方面,inode值等(ls大目录可看到这个值增加)缓存将要放到硬盘里的数据 
cached——缓存,缓存从硬盘读出来的数据用于已打开的文件:
当你读写文件的时候,Linux内核为了提高读写性能与速度,会将文件在内存中进行缓存,这部分内存就是Cache Memory(缓存内存)。即使你的程序运行结束后,Cache Memory也不会自动释放。这就会导致你在Linux系统中程序频繁读写文件后,你会发现可用物理内存会很少。 
其实这缓存内存(Cache Memory)在你需要使用内存的时候会自动释放,所以你不必担心没有内存可用。 
只有当 free 减去 cached 剩下的这部分内存情况紧张时,才有可能出现应用程序没有足够内存使用的情况 
注意-/+ buffers/cache: 5 66这行。 
前个值表示-buffers/cache—–>不包括缓存,应用程序物理内存使用情况,即 -buffers/cache=used-buffers-cached ,所以此时应用程序才用了5M内存 。 
后个值表示+buffers/cache—–>所有可供应用程序使用的内存大小,free加上缓存值,即+buffers/cache=free+buffers+cached ,所以此时还有接近66M 内存可供程序使用。 

swap:
交换分区、交互内存:

交互分区属于硬盘空间,做为内存不足时的临时内存使用
swap 主要的功能是当实体内存不够时,则某些在内存当中所占的程序会暂时被移动到 swap 当中,让实体内存可以被需要的程序来使用。另外,如果你的主机支持电源管理模式, 也就是说,你的 Linux 主机系统可以进入“休眠”模式的话,那么, 运行当中的程序状态则会被纪录到 swap 去,以作为“唤醒”主机的状态依据! 另外,有某些程序在运行时,本来就会利用 swap 的特性来存放一些数据段, 所以, swap 来是需要创建的!只是不需要太大!


1.2系统:

虚拟内存是操作系统内核为了对进程地址空间进行管理(process address space management)而精心设计的一个逻辑意义上的内存空间概念。我们程序中的指针其实都是这个虚拟内存空间中的地址。比如我们在写完一段C++程序之后都需要采用g++进行编译,这时候编译器采用的地址其实就是虚拟内存空间的地址。因为这时候程序还没有运行,何谈物理内存空间地址?凡是程序运行过程中可能需要用到的指令或者数据都必须在虚拟内存空间中。既然说虚拟内存是一个逻辑意义上(假象的)的内存空间,为了能够让程序在物理机器上运行,那么必须有一套机制可以让这些假象的虚拟内存空间映射到物理内存空间(实实在在的RAM内存条上的空间)。这其实就是操作系统中页映射表(page table)所做的事情了。内核会为系统中每一个进程维护一份相互独立的页映射表。页映射表的基本原理是将程序运行过程中需要访问的一段虚拟内存空间通过页映射表映射到一段物理内存空间上,这样CPU访问对应虚拟内存地址的时候就可以通过这种查找页映射表的机制访问物理内存上的某个对应的地址。“页(page)”是虚拟内存空间向物理内存空间映射的基本单元。
1
1.3进程
拿java举例


VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS- Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS- Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS

二、问题:
内存使用率88%高于80%报警。

三、原因:
指标含义:内存使用率百分比(%)。
指标解释:容器的内存使用率是读取物理机cgroup下面的文件的,获取的是整个容器的内存使用率并不是针对某个程序。物理机内存使用率和使用free命令计算结果是一致的。物理机和容器两者内存计算数据是独立的
计算公式近似等于为:进程使用的(物理内存和本地内存和共享内存)、未被换出的物理内存大小,单位kb。RES=CODE+DATA

四、解决步骤:
用top中查看RES是操作系统角度看jvm的内存占用。
用jmap查看的堆内存,是用jvm的角度看jvm内部程序的内存占用。
存在差异是因为jvm有一些共享库和共享内存,被操作系统计入RES中,但未被jvm计入

1、查看哪些应用占用内存比较大

查看哪几个进程内存占用最高:top -c,输入大写M,以内存使用率从高到低排序
1


PID : 进程id
PPID : 父进程id
RUSER : Real user name
UID : 进程所有者的用户id
USER : 进程所有者的用户名
GROUP : 进程所有者的组名
TTY : 启动进程的终端名。不是从终端启动的进程则显示为 ?
PR : 优先级
NI : nice值。负值表示高优先级,正值表示低优先级
P : 最后使用的CPU,仅在多CPU环境下有意义
%CPU : 上次更新到现在的CPU时间占用百分比
TIME : 进程使用的CPU时间总计,单位秒
TIME+ : 进程使用的CPU时间总计,单位1/100秒
%MEM : 进程使用的物理内存百分比
VIRT : 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
SWAP : 进程使用的虚拟内存中,被换出的大小,单位kb。
RES : 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
CODE : 可执行代码占用的物理内存大小,单位kb
DATA : 可执行代码以外的部分(数据段+栈)占用的物理内存大小,单位kb
SHR : 共享内存大小,单位kb
nFLT : 页面错误次数
nDRT : 最后一次写入到现在,被修改过的页面数。
S : 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
COMMAND : 命令名/命令行
WCHAN : 若该进程在睡眠,则显示睡眠中的系统函数名
Flags : 任务标志,参考 sched.h

默认情况下仅显示比较重要的 PID、USER、PR、NI、VIRT、RES、SHR、S、%CPU、%MEM、TIME+、COMMAND 列。可以通过下面的快捷键来更改显示内容。 更改显示内容
通过 f 键可以选择显示的内容。按 f 键之后会显示列的列表,按 a-z 即可显示或隐藏对应的列,最后按回车键确定。
按 o 键可以改变列的显示顺序。按小写的 a-z 可以将相应的列向右移动,而大写的 A-Z 可以将相应的列向左移动。最后按回车键确定。
按大写的 F 或 O 键,然后按 a-z 可以将进程按照相应的列进行排序。而大写的 R 键可以将当前的排序倒转

2、通过jmap -heap 进程id 命令排除是由于堆分配内存问题。得到如下结果

Attaching to process ID 542287, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.20-b23

using thread-local object allocation.
Garbage-First (G1) GC with 43 thread(s)
//堆配置信息
Heap Configuration:
   //指定 jvm heap 在使用率小于 n 的情况下 ,heap 进行收缩 ,Xmx==Xms 的情况下无效 , 如 
   MinHeapFreeRatio         = 40
   //指定 jvm heap 在使用率大于 n 的情况下 ,heap 进行扩张 ,Xmx==Xms 的情况下无效 , 如 
   MaxHeapFreeRatio         = 70
   //最大堆空间
   MaxHeapSize              = 5393874944 (5144.0MB)
   //设置Yong Generation的初始值大小,一般情况下,不允许-XX:Newratio值小于1,即Old要比Yong大。
   NewSize                  = 1363144 (1.2999954223632812MB)
   //设置Yong Generation的最大值大小
   MaxNewSize               = 3235905536 (3086.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   //设置年轻代和老年代的比例,默认情况下,此选项为2
   NewRatio                 = 2
   //默认eden空间大小和survivor空间大小的比,默认情况下为8
   SurvivorRatio            = 8
   //初始化元空间大小,控制gc阀值,gc后动态增加或者降低元空间大小,默认情况下平台的不同,步长为12-20M
   MetaspaceSize            = 209715200 (200.0MB)
   //默认1G,这个参数主要是设置Klass Metaspace的大小,不过这个参数设置了也不一定起作用,前提是能开启压缩指针,假如-Xmx超过了32G,压缩指针是开启不来的。如果有Klass Metaspace,那这块内存是和Heap连着的。
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   //为类元数据分配的最大空间量
   MaxMetaspaceSize         = 536870912 (512.0MB)
   //堆内存中一个Region的大小可以通过-XX:G1HeapRegionSize参数指定,大小区间只能是1M、2M、4M、8M、16M和32M,总之是2的幂次方,如果G1HeapRegionSize为默认值,则在堆初始化时计算Region的实践大小
   G1HeapRegionSize         = 2097152 (2.0MB)
//堆的使用信息
Heap Usage:
G1 Heap:
//区域数量
   regions  = 2572
//堆内存大小
   capacity = 5393874944 (5144.0MB)
//已经使用了
   used     = 3216639400 (3067.62638092041MB)
//空闲着的堆内存
   free     = 2177235544 (2076.37361907959MB)
   59.63503850933923% used
以下同理
G1 Young Generation:
Eden Space:
   regions  = 425
   capacity = 2650800128 (2528.0MB)
   used     = 891289600 (850.0MB)
   free     = 1759510528 (1678.0MB)
   33.62341772151899% used
Survivor Space:
   regions  = 1
   capacity = 2097152 (2.0MB)
   used     = 2097152 (2.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 1109
   capacity = 2740977664 (2614.0MB)
   used     = 2323252648 (2215.62638092041MB)
   free     = 417725016 (398.37361907958984MB)
   84.75999927009985% used

35394 interned Strings occupying 3871104 bytes.

截止到这里,本次问题已经找到了。因为设置的堆空间分配额比较大。将近63% =5g/8g。内存使用率计算公式为code+Data。本地内存和共享内存和可执行代码以外的部分(数据段+栈)等,当堆内存还没有达到full gc的时候,内存使用率问题就显现出来了。将内存分配最大值设为4g.并重新更新配置文件,发布应用


但是这里存在一个问题,内存使用率高,刚才提到的一个情况就是堆内存接近最大值不会进行fullgc么?fullgc不就帮你回收堆空间了么?
这是个好的问题。
实际上他确实发生fullgc了,我们可以查到


那么为什么没有解决内存使用率问题呢?而是将堆分配额重新调整之后,内存使用率才降下去。
简单点说,就是如果你的项目需要人手10个人,你跟领导要了10个人,当项目只是第一个迭代干完了,那么你会不会立马将其中的5个人交给领导?答案是不会的,但是如果现在重新分配,领导说我就给你5个人下一个迭代你先干着,这样你就需要必须上交5个人。具体的点这里。详细的说明内存是如何管理的。

如果上面也没有问题定位到原因,则继续按照步骤排查

3、找到最耗内存的对象
jmap -histo 进程ID(带上:live则表示先进行一次FGC再统计,如jmap -histo:live 进程ID)


可以看到上面最大的实例进程 将近30M。

4、导出内存转储快照dump文件:
4.1、通过java进程命令定位 系统进程并使用jmap工具dump文件。

ps -ef | grep java 
生成dump文件的命令:
jmap -dump:format=b,file=20181218.dump 16048
file后面的是自定义的文件名,最后的数字是进程的pid。

4.2、使用jvisualvm来分析dump文件:

jvisualvm是JDK自带的Java性能分析工具,在JDK的bin目录下,文件名就叫jvisualvm.exe。
jvisualvm可以监控本地、远程的java进程,实时查看进程的cpu、堆、线程等参数,对java进程生成dump文件,并对dump文件进行分析。
假设我现在下载下来的是txt文件也可以直接扔给jvisualvm来分析。

4.3、使用方式:直接双击打开jvisualvm.exe,点击文件->装入,在文件类型那一栏选择堆,选择要分析的dump文件,打开。

导入文件以后界面如下图:

可以看到,dump文件里记录的堆中的实例,总大小大概5392M左右,(用第一行的实例大小除以百分比就能算出来)

4.4、现在看堆转储的线程问题


每一个部分的含义如下:
“http-nio-1601-Acceptor-0” 线程名称
daemon 线程的类型
prio=5 线程的优先级别
tid=290 线程ID
RUNNABLE 线程当前的状态

4.5、线程当前的状态是我们主要关注的内容。
dump文件中描述的线程状态

runnable:运行中状态,在虚拟机内部执行,可能已经获取到了锁,可以观察是否有locked字样。
blocked:被阻塞并等待锁的释放。
wating:处于等待状态,等待特定的操作被唤醒,一般停留在park(), wait(), sleep(),join() 等语句里。
time_wating:有时限的等待另一个线程的特定操作。
terminated:线程已经退出

4.6、进程的区域划分

进入区(Entry Set):等待获取对象锁,一旦对象锁释放,立即参与竞争。
拥有区(The Owner):已经获取到锁。
等待区(Wait Set):表示线程通过wait方法释放了对象锁,并在等待区等待被唤醒。

4.7、方法调用修饰

locked: 成功获取锁
waiting to lock:还未获取到锁,在进入去等待;
waiting on:获取到锁之后,又释放锁,在等待区等待;

4.8、OQL(对象查询语言)
如果需要根据某些条件来过滤或查询堆的对象,比如现在我们查询下系统中类加载器一共有几种?

4.9、引导计数
引导类 (即 JVM 在未使用任何 java.lang.ClassLoader 实例的情况下加载的 Java 平台类) 的计数
其余展示的与名称一样

5、统计进程打开的句柄数:ls /proc/进程ID/fd |wc -l

6、统计进程打开的线程数:ls /proc/进程ID/task |wc -l

7、使用jstat查看进程的内存使用情况
jstat [Options] vmid [interval] [count]
Options,选项,我们一般使用 -gcutil 查看gc情况
vmid,VM的进程号,即当前运行的java进程号
interval,间隔时间,单位为秒或者毫秒
count,打印次数,如果缺省则打印无数次

  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00 100.00  32.20   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775
  0.00 100.00  32.57   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775
  0.00 100.00  32.94   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775
  0.00 100.00  33.31   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775
  0.00 100.00  33.62   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775

 S0C:年轻代中第一个survivor(幸存区)的容量 (字节) 
S1C:年轻代中第二个survivor(幸存区)的容量 (字节) 
S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节) 
S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节) 
EC:年轻代中Eden(伊甸园)的容量 (字节) 
EU:年轻代中Eden(伊甸园)目前已使用空间 (字节) 
OC:Old代的容量 (字节) 
OU:Old代目前已使用空间 (字节) 
PC:Perm(持久代)的容量 (字节) 
PU:Perm(持久代)目前已使用空间 (字节) 
YGC:从应用程序启动到采样时年轻代中gc次数 
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s) 
FGC:从应用程序启动到采样时old代(全gc)gc次数 
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s) 
GCT:从应用程序启动到采样时gc用的总时间(s) 
NGCMN:年轻代(young)中初始化(最小)的大小 (字节) 
NGCMX:年轻代(young)的最大容量 (字节) 
NGC:年轻代(young)中当前的容量 (字节) 
OGCMN:old代中初始化(最小)的大小 (字节) 
OGCMX:old代的最大容量 (字节) 
OGC:old代当前新生成的容量 (字节) 
PGCMN:perm代中初始化(最小)的大小 (字节) 
PGCMX:perm代的最大容量 (字节) 
PGC:perm代当前新生成的容量 (字节) 
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比 
S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比 
E:年轻代中Eden(伊甸园)已使用的占当前容量百分比 
O:old代已使用的占当前容量百分比 
P:perm代已使用的占当前容量百分比 
M:元空间中已使用的占当前容量百分比
S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节) 
S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节) 
ECMX:年轻代中Eden(伊甸园)的最大容量 (字节) 
DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满) 
TT: 持有次数限制 
MTT : 最大持有次数限制


8、.用jstack查看一下
jstack pid | grep tid(线程ID) -A 30

案例5:

发布了39 篇原创文章 · 获赞 0 · 访问量 765

猜你喜欢

转载自blog.csdn.net/qq_15458763/article/details/103932984