实战 - 分析java项目线上内存泄漏、内存溢出、频繁GC的原因(1)

*/

public static void main(String[] args) throws Exception {

executor.setMaximumPoolSize(50);

// 为什么是死循环?因为在生产环境中会有源源不断的数据需要处理,我们无法模拟线上环境, 所以用死循环代替;

for (;😉{

modelFit();

Thread.sleep(100);

}

}

private static void modelFit(){

List taskList = getAllCardInfo();

taskList.forEach(info -> {

// do something

executor.scheduleWithFixedDelay(() -> {

//do sth with info

info.m();

}, 2, 3, TimeUnit.SECONDS);

});

}

private static List getAllCardInfo(){

List taskList = new ArrayList<>();

for (int i = 0; i < 100; i++) {

CardInfo ci = new CardInfo();

taskList.add(ci);

}

return taskList;

}

}

启动

=================================================================

启动时加入参数-Xms200M -Xmx200M -XX:+UseParallelGC -XX:+PrintGC -XX:+HeapDumpOnOutOfMemoryError,这段代码运行后,老年代的内存占用会慢慢升高,待内存占用到达顶峰时,会频繁Full GC(回收老年代垃圾),直到撑爆内存,最后会导致OOM异常:Exception in thread "pool-1-thread-1" java.lang.OutOfMemoryError: GC overhead limit exceeded;也就是内存溢出;

运行监控

===================================================================

运行一段时间后,可以看到一直不停地full GC,并且有些线程的内存已经溢出报出OOM错误;

在这里插入图片描述

解决方案:使用arthas

============================================================================

在用arthas 的dashboard命令看一下内存使用情况,这一看,我的天,老年代的内存已经使用了98.64%,并且已经进行了3914次的GC,也就是说Full GC进行了3914次清理都没清掉那些垃圾;

在这里插入图片描述

到这时候我们就已经确定发现了内存泄漏,接下来的工作就是要找到是那些顽固的对象没被清理调,然后在做出相应的调整;

首先要分析堆内存有哪些对象,这里使用到一个arthas的工具:heapdump,这个命令类似jdk的jmap,使用arthas导出堆转储文件命令;

[arthas@28747]$ heapdump --live /Users/mac/Downloads/dump.hprof

Dumping heap to /Users/mac/Downloads/dump.hprof …

Heap dump file created

导出后是一个二进制文件,这个文件直接打开看到的是乱码的,所以我们需要借助一些工具,这边有2个选择,用jhatjvisualVM,因为jhat用的不多,所以我们用大家常用的jvisualVM.

使用jvisual VM加载堆转储文件dump.hprof后如下图:

在这里插入图片描述

由此结果可以看到,Date对象和Bigdecimal对象一直无法回收,而每一个定时任务就会创建一个Date对象和Bigdecimal对象,到现在为止已经有55万个了,因为只增不减,撑爆内存是迟早的事。

解决方案二:使用jmap

===========================================================================

先使用jps命令找到正在运行的java进程id

macdeMacBook-Pro:Downloads mac$ jps

24240 App

29410 Launcher

29411 T15_FullGC_Problem01

2851

29414 Jps

29031 Main

我们运行的类为T15_FullGC_Problem01,对应的进程id为29411,记住这个进程id;

接着用jmap命令查看这个进程id,看看内存占用排行前十的对象有哪些:

jmap -histo:live 29411| head -10

  • -histo:live :表示只查看存活的对象

  • head - 10 :这是linux自带的命令,表示查看头部前十行内容;

运行后,结果和上面的jvisual分析结果差不多,Date对象和Bigdecimal对象都是在CardInfo方法里面的。所以排行最高的就是这三个;

macdeMacBook-Pro:Downloads mac$ jmap -histo:live 29411| head -10

num #instances #bytes class name


1: 447100 32191200 java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask

2: 447126 17885040 java.math.BigDecimal

3: 447100 14307200 com.gc.T15_FullGC_Problem01$CardInfo

4: 447100 10730400 java.util.Date

5: 447100 10730400 java.util.concurrent.Executors$RunnableAdapter

6: 447100 7153600 com.gc.T15_FullGC_Problem01$$Lambda$2/664223387

7: 1 2396432 [Ljava.util.concurrent.RunnableScheduledFuture;

为什么DateBigdecimal对象没被回收

===========================================================================================

是因为modelFit()方法出现了问题,

private static void modelFit(){

List taskList = getAllCardInfo();

taskList.forEach(info -> {

// do something

executor.scheduleWithFixedDelay(() -> {

//do sth with info

info.m();

}, 2, 3, TimeUnit.SECONDS);

});

}

仔细看这个方法内的代码,关于这段代码可以有2种解释,

1、taskList链接着info对象

infotaskList中的元素,每个info元素都taskList这个对象所引用着,每次定时任务执行完后,线程内的对象都会被垃圾回收器清理掉,但是info这个对象不属于定时任务线程内的对象,所以没被清理掉;按理说taskList内的所有对象都遍历完了之后,应该会将taskList给清除掉,但是taskList还有个别元素在线程中,他们之间的引用还在,既然有引用,也就自然不会被清理;引用关系如下图在这里插入图片描述

2、线程引用这info对象

infotaskList中的元素,每个info元素都taskList这个对象所引用着,这个定时任务executor.scheduleWithFixedDelay(() -> { }); 花括号的内容其实是在另一个域里面了,虽然info.m()这个方法已经执行完了,但是执行完后线程并没有被回收,因为线程的核心线程数和最大线程数都设置为50,所以线程执行完后一直在那挂着,既然线程还没回收,线程中GC Roots对info对象的引用就一直在,既然引用还在,垃圾回收器就不会回收被引用的对象;引用关系如下图

在这里插入图片描述

解决方案

===================================================================

要解决这个问题,就得解决引用的问题,让对象引用随着线程的执行完毕而清理掉,所以只需要修改modelFit()方法为以下代码即可解决问题。以下这段代码,taskList 在线程内执行,一旦线程执行完后taskList也会随着线程一起被回收掉,另外taskList内的所有CardInfo也会被回收,紧接着DateBigdecimal对象就没有引用了,也会被垃圾回收器回收掉,到这里也就解决内存泄漏问题。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

在面试前我整理归纳了一些面试学习资料,文中结合我的朋友同学面试美团滴滴这类大厂的资料及案例

MyBatis答案解析
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

大家看完有什么不懂的可以在下方留言讨论也可以关注。

觉得文章对你有帮助的话记得关注我点个赞支持一下!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
(img-J8GKaiNG-1713643555332)]

[外链图片转存中…(img-idpzFbSA-1713643555333)]
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

大家看完有什么不懂的可以在下方留言讨论也可以关注。

觉得文章对你有帮助的话记得关注我点个赞支持一下!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值