首页 > 应用开发 > Java > 深入理解JVM——案例实战(一)
在日常场景进行发邮箱,短信以及APP 的推送消息一些特别活动。
这种业务的特点是短时间之内会有大量的用户进入APP进行参与,这时候系统的压力会突然增加。
在业务流量高峰的时候,CPU的使用率十分十分高,并且直接导致系统卡死,无法进行任何请求的处理,在系统重启之后会好一段时间,但是后面又会马上卡死。
通过这种思路排查,结果果然发现FULL GC的频率十分高,居然 一分钟一次FULL GC,频率实在是太高了。
继续排查发现使用 jstat发现并不存在内存不合理的情况,并且对象也是正常进入老年代,同时永久代的内存居然也是正常的。
这时候又会考虑一个问题,一分钟一次FULL GC,证明老年代空间是不够的,虽然新生代进入老年代是正常的,但是如果老年代 本身对象就非常多,会不会也会出现问题呢?按照这个思路继续排查,果然发现老年代GC之后 居然还有那么多对象存活。
真相大白,原因就是老年代被大量对象占满了,很容易触发FULL GC,我们可以使用 Jmap的工具排查这里面的内容,当然,也可以使用 mat(memory anaylyze tool)进行排查,但是本文不涉及工具的使用介绍,大致介绍一下mat的处理流程:
其实按照排查思路进行一步步排查,要找到问题其实并不是很难。
业务就直接跳过,这里重点关注问题分析和解决流程。
不用说,标题已经暴露了一切,但是究竟是如何分析出来的?这里也不兜圈子,直接给一张图,:
从这里看到
java.lang.Thread的主线程main 线程,局部变量居然占用了**24.97%**的内存的对象。这里告诉你问题出现在
java.lang.Object[]数组,这个数组占用大量的内存。
在1的下面有一行蓝色的 Details,进入之后可以看到下面的内容:
在
Problem Supspect 3里面也可以看到这里面占用了大量的String对象。
从这里可以看到在main线程里面,有一个arrayList集合占用了几乎所有的内存,这个List显然也是Object[]的数组,并且在内容里面存在Demo1$Data的对象实例。
从这个分析我们知道了如何分析出内存占用的问题,其实大胆猜测加上实用工具测试可以基本都可以验证出问题。
知道了占用是因为Object[]数组的问题,接着来看下链路追踪的情况:
如上图所示,我们点击 statictrace进入到具体的代码界面:
答案在最下面的图:
我们可以明显的看到是String的问题,通过代码搜索发现有一个
String.split可能是产生问题的原因。
这里就涉及一个JDK源代码的问题了:
在JDK6的版本,一个字符串的底层是基于下面的形式进行存储的,比如"yes yes yes yes"使用空格切分是如下的形式:
["yes","yes","yes","yes"]
但是到了Jdk7,他给每个切分出来的字符串都创建了一个新的数组,意思就是说每次切分都切分出一个新的数组,这里可能没法理解,所以我们给出代码:
这个
sublist毫无疑问就是罪魁祸首了,导致JDK版本升级了之后内存占用爆高也是这个代码,这个代码干了啥呢?
这个也是典型的面试题,可用看到返回了当前List的视图,同时这个视图会随着数组的改变而改变,关于这个对象细节百度一大堆,这里不讨论,这里需要关注的是这个
new。
到这里相信读者也清楚为什么
split()方法会导致大量的
Object[]数组被构建出来,
SubList底层依然是一个数组!
说白了还是代码的质量问题,不用想可以知道需要从代码层面修复问题,解决fot循环里面的
split()方法。
所以字符串的操作尤其需要谨慎,因为字符串天生的不可变的特性,使用频率非常高的同时也很容易出现问题。
这篇文章内容不多,主要为下面两个点:
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70000438/viewspace-2785189/ ,如需转载,请注明出处,否则将追究法律责任。