打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
java list 占用内存不释放

JVM应该可以算Java中最为核心的部分了,其中开箱即用的内存管理又是JVM中的核心组成部分。我们都知道JVM的内存管理具有垃圾回收功能(Java Garbage Collector),编码时只需要new而无需主动的释放(类似于C++中的delete操作),所以Java中比较少出现内存泄露的情况。比较少出现,并不一定就不会出现,那么Java程序在什么时候会出现内存泄露呢?出现内存泄露该如何排查呢?

Java程序为什么会有内存泄露

什么是内存泄露呢?内存泄露可以定义为:当一个对象已经不再被应用程序使用以后,它所占用的内存没有得到及时的释放,导致内存使用量随着时间的推移不断的增加,最终导致应用程序崩溃的现象(Java中会在new新对象的时候抛出OutOfMemoryError)。

对于Java程序而言,当一个Object已经不会被程序所使用,但是它还被其它对象所引用,从而导致GC的时候无法被回收,从而导致内存泄露。

下图比较直观的展示了Java内存泄露发生的情形:

从上图我们可以把对象分为两大类:被引用的和不被引用的。垃圾回收的时候不释放那些不被引用的对象,被引用的对象则不会被释放,即便是这些对象后续一直都没有被用过。

定位Java内存泄露是比较麻烦的事情,需要使用到JVM提供的多种工具来进行内存分析,并且往往还需要结合代码进行分析。

Java堆内存泄露

首先我们来看看Java程序中最为常见的堆内存泄露。

如果想要直观的模拟出堆内存泄露,我们需要设置一一个较小的堆大小(内存泄露是独立存在的,和对内存大小无关,不过较小的堆内存大小可以更直观的观察到内存泄露)。

我们可以通过如下两个启动参数设置对内存大小:

通过静态变量来演示内存泄露

首先我们通过如下的代码来看看看看正常Java代码运行时的内存变化情况:

启动参数:-Xms20m -Xmx20m

运行是的内存变化图:

在我们的代码中我们每秒调用一次test方法,这个方法中会往list中添加数据,但是在方法返回之后这些数据就处于无引用状态了(并不一定会立马进行GC),我们每10秒调用一次System.gc()(full GC)。从内存监控图上能够直观的观察到堆内存的使用变化曲线。

接下来我们对上面的代码做一下修改:

静态变量保存了过多不在使用的对象引用导致的内存泄露是我们编码过程中最为常见的一种内存泄露方式。下面的代码就演示了这个情况:

为了更加直观的观察内存变化,我稍微调整了一下每次插入的数据个数,启动参数仍旧和上面一下。这个时候我们能够得到如下图所示的内存变化曲线:

从曲线中我们可以发现,堆内存使用量一直在增加,并没有和前一个示例一样在full GC后释放没有使用的堆内存,每次调用test方法后添加到list中的对象都会被list所引用,所以GC是不会收集并释放这些内存的。

如何定位和解决内存泄漏

Java的内存泄漏定位一般是比较困难的,需要使用到很多的实践经验和调试技巧。下面是一些比较通用的方法:

  1. 可以添加-verbose:gc启动参数来输出Java程序的GC日志。通过分析这些日志,可以知道每次GC后内存是否有增加,如果在缓慢的增加的那,那就有可能是内存泄漏了(当然也需要结合当前的负载)。如果无法添加这个启动参数,也可以使用jstat来查看实时的gc日志。如果条件允许的话可以考虑使用jvisualvm图形化的观察,不过线上的话一般没这个条件。
  2. 当通过dump出堆内存,然后使用jvisualvm查看分析,一般能够分析出内存中大量存在的对象以及它的类型等。我们可以通过添加-XX:+HeapDumpOnOutOfMemoryError启动参数来自动保存发生OOM时的内存dump。
  3. 当确定出大对象,或者大量存在的实例类型以后,我们就需要去review代码,从实际的代码入手来定位到真正发生泄漏的代码。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
HotSpot JVM常用参数(选项)设置
从一次线上故障思考 Java 问题定位思路
Java逃逸分析
【Java面试题第一期】有没有jvm调优经验?调优方案有哪些?
如何监控GC及内存问题解决方案概述
Java性能调优工程的几点建议
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服