首先推荐一本书《深入理解Java虚拟机——JVM高级特性与最佳实践(第2版)》,应该多读几遍。
然后分享一些这段时间我查看的一些资料 :
另外因为JVM8的内存模型发生了变化,移除了永久代添了metaSpace 。也查看了这些方面相关的文章。推荐你假笨这篇文章 JVM源码分析之Metaspace解密
有了上述相关的知识储备就开始分析 门店宝Service 内存占用过大的问题。
命令 :
jps -l
显示所有的 Java 进程ID 以及启动类,通过启动类可以可容易的看出 Service 的PID 是 7600。JDK的大部分命令都需要使用到PID这个参数,用于界定所需要显示的数据到底来源于哪个进程。
jstat -gc <pid>
Jstat 命令用于查看堆内存各部分的使用情况,命令的参数很多,具体可以参考这篇文章Jstat命令使用。
-gc 参数用于查看Java内存分布以及GC信息。
下面的图是在生成上获取的门店宝Service内存和GC信息。
字段名称意义如下:
S0C — Heap上的 Survivor space 0 区分配的大小
S0U — Heap上的 Survivor space 0 区已使用的大小
其他的类似,
E — Heap上的 Eden space 区
O — Heap上的 Old space 区
M — meta space 区
CCS ——压缩类
YGC — 从应用程序启动到采样时发生 Young GC 的次数
YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒)
FGC — 从应用程序启动到采样时发生 Full GC 的次数
FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)
第二个图是在第一个图三天后的数据 , 新生代的内存使用是没问题的,old generation 的内存使用量很低但是也不至于影响使用,不过在以后我们可以对这两块的内存进行相应的调节。
另外一个很明显的问题就是GC发生的频率是非常低的。首先就是线上的数据太少,其次也是因为目前在门店宝Service上我们并没有对JVM参数做任何修改,所以默认的JVM的内存将会自我扩展,直至利用到系统所有的内存信息,所以这也就产生了随着系统运行时间变长,服务占用的内存越来越大的问题。
jmap -dump:format=b,file=文件名 [pid]
dump当前系统,根据dump文件我们可以分析当前系统中存在的内存问题。
分析dump文件的工具很多,JDK自带的Jhat,Eclipse也有相关的插件。
我使用的是Eclipse Memory Analyzer,功能很强大,能够生成各种报表,另外可以在不同的时间生成不同的dump,然后通过工具分析两个dump的内存变化。
我仅仅使用了其中一个功能,查看dominator_tree,就是一个内存分配的树形结构。
很明显的就可以发现GRPC生成的类消耗了大量的内存。
因为在门店宝Service中,我们将方法和Bean都写在一个proto文件内,而且一个服务里写了很多的请求,这就导致了grpc生成的类很大。但是实际上我们这是只用了其中一个内部类,所以这样产生了不少无意义的内存使用。如果看完了上面的文章也可以发现,JVM 的GC对 存活时间短,但是内存占用大的类是很不友好的。所以以后尽量使用小方法,小类,这样对于系统性能的提升还是比较明显的。
图形化工具 :
相比较命令,图形化的工具使用起来更直观。
推荐两个JDK自带的工具jvisualvm 和的JMC。不过理论上JMC是不能用于商业途径的。
jvisualvm有一些功能强大的插件,比如直接查看GC情况,自动执行Btrace脚本等等。
工具点开即用,使用方法十分简单,但是涉及到具体问题分析的时候,等到以后有机会学习到了,再和各位分享。
图形工具很厉害的一点在于我们可以远程事实的监测环境的JVM运行情况,不过鉴于我们现在使用到了docker,远程使用Visual VM或者 JMC相对麻烦一些。一下的两篇文章可以作为远程链接的参考。
How to connect VisualVM to Docker
How do I attach VisualVM to a simple Java process running in a Docker container