【Developer Log】Java的内存使用、垃圾回收和内存泄漏

事情起因

我们正在进行一个业务的12小时的大压测试,有同事投诉说,我的Java程序出现了内存泄漏的问题的。通过Jconsole监控,出现下图的情况。是耶非耶?

我对自己的程序也有监控,监控各队列、堆栈、线程的数量。这些数量没有出现异常,在大压测试之后数值回复空闲状态。一般来讲,这种情况基本上排除出现内存泄漏的情况。之前有48小时的测试,没有出现这个问题,不太可能是期间进行数据库优化处理导致的。

为此,对Java的内存机制进行了解,同时也进行了进一步深入测试。


Java内存分配情况

推荐阅读《Java内存与垃圾回收调优》一文,原文出处:http://www.journaldev.com/2856/java-jvm-memory-model-and-garbage-collection-monitoring-tuning

查看Java程序在java VM的内存分配情况,jdk提供两个工具jconsole和jvisualvm,其中jvisualvm下载Visual GC插件。通过这两个工具,我们可以更好地了解java vm的内存分配和垃圾回收机制。

(1)堆区和非堆区

vm为程序分配的内存,分为堆区(heap)和非堆区。下图是Jconsole中显示的内存区:

非堆区包括Metaspace、Code cache,和Compress Class Space,这是java 8,而在jdk7以及之前的版本的是Perm Gen(Permanent Generation)永久区和Code cache。用于存放在应用中描述的类和方法。Metaspace和Perm Gen的差别不在本文讨论,我们将含糊认同。

堆区则有三大部分:1、Eden;2、两个Survivor区,S0和S1;3、Old。

垃圾回收和堆区以及Metaspace有关。下面是jvisualvm中Visual GC对这几个区的图式。

(2)内存的分配

在VM中,为程序的堆区和非堆区的各个区都分配了空间(最大值空间),大小可能和具体的寻址机制有关,8G内存的64位机器的最大值是4G内存64位机器的2倍。例如4G内存机器中Old Gen为637.5M,8G内存机器为1.275G。如果内存使用超过了最大值的限制,无论发生在哪个区,无论是在Eden Space还是Old Gen Space,就会出现内存泄漏,OutOfMemeoryError的错误,程序崩溃。

VM并不是一下就给程序使用所有的空间。显示提交使用某个空间,当这个空间满时,触发GC操作,然后提交使用一个新的空间大小(大小不一定和旧的一样)。

因此我们会看到锯齿状的内存使用。例如分配10M的空间,程序从1M开始,不断占用空用,到了10M,则触发GC操作,释放空间,空间又变为1M,并分配10M空间或者8M,或者12M。

在签名Visual GC图中,可以看到Eden Space的最大空间为317.5M,已提交15M,使用了10.6M,进行了554次GC,Old Gen最大空间为637.5M,提交25.5M,已使用11.362M,进行了2次GC。

(3)GC回收

根据参考,内存分配和GC回收如下:

  • 大多数新建的对象都位于Eden区。
  • 当Eden区被对象填满时,就会执行Minor GC。并把所有存活下来的对象转移到其中一个survivor区。
  • Minor GC同样会检查存活下来的对象,并把它们转移到另一个survivor区。这样在一段时间内,总会有一个空的survivor区。
  • 经过多次GC周期后,仍然存活下来的对象会被转移到Old Gen空间。
  • 年老代内存里包含了长期存活的对象和经过多次Minor GC后依然存活下来的对象。通常会在老年代内存被占满时进行垃圾回收。老年代的垃圾收集叫做Major GC。

分级内存和分级回收,是减少GC对程序的影响。所有的垃圾收集都是“Stop the World”事件,因为所有的应用线程都会停下来直到操作完成(所以叫“Stop the World”)。因为Eden里的对象都是一些临时(short-lived )对象,执行Minor GC非常快,所以应用不会受到(“Stop the World”)影响。由于Major GC会检查所有存活的对象,因此会花费更长的时间。

此外,对于Perm Gen,永久代的对象在full GC时进行垃圾收集。一般而言,这部分与静态方法、对象有关,其数量通常是固定的,很少会发生变化。

程序是否出现内存泄漏,我们可以重点监控Old Gen空间。 


案例分析:被测程序的特点和进一步的测试结果

Eden的GC操作是很频繁的,通常几秒就发生一次,甚至2~3秒。如果业务能够在这段时间内完成,就不会积累到Old Gen中。例如java servlet,页面的处理时间是很短的,因此内存堆的占用情况由Eden确定。

但是我的程序中业务处理时间要长得多,在12小时大压测试中,一个业务持续时间为6秒(不是什么都是web页面处理哦,例如跟踪一个呼叫,那就是几十秒),因此业务对象会被放置到Old Gen中,并慢慢累积,直至到达其提交空间,进行GC操作。

VM为Old Gen分配多少提交空间,不清楚是基于什么进行优化,有可能经过多次GC后才找到一个最为合适的提交空间大小。

我对程序进行了进一步的测试,为了更快地了解内存使用情况,加大了压力,找内存较小的机器,以及拉长业务持续时间至15秒(这个其实影响不大,只要放到Old Gen就可以了)。

在测试中,在2G内存的32位windows机器中,在某压力下,第一次分配20M,第二次分配多一些,第三次更多一些,慢慢增加,直至到一个稳定值。因此可能看到总体内存的消耗稳步增加直至某个稳定值。

Windows机器很多意外的,晚上因为windows 10申请重启了一次,后来移到阿土上。增加更高的压力,第一次分配40M,第二次、第三次减少,然后增加,最后稳定在30M(29.5M和30.5M浮动),见下图。Visual GC上的值和Jconsole上值的轻微差异,应该是1024和1000的差异。

在呼叫了60小时,我们看看结果:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值