最近在拜读周志明的《深入理解Java虚拟机》,看了Java的内存管理机制以及Jvm优化相关内容。于是,下载了sun提供的Jvm检测工具VisualVm。
VisualVm下载地址:https://visualvm.java.net/
安装就很简单了,在此略过。
作者博客地址:http://icyfenix.iteye.com/
可以下载PDF版的《Java虚拟机规范 (Java SE 7 中文版)》
左侧显示当前系统中运行的虚拟机进程,双击连接后可以监测虚拟机的运行状况。
由于我已经安装过了,如果没有安装,目录中会有VisualGC。该工具可以查看不同内存区域的使用情况和GC情况,效果如图
使用jps命令,可以查看系统中运行的jvm进程
$ jps
2064 TestMain
1389 Main
2267 Jps
1009
287
忽然发现个奇特的东西,就是TestMain进程,这个名字好奇怪,不是我创建的,在“活动监视器”中搜了一下PID,发现是公司的Hosts管理工具。奇怪的是,一款小小的Hosts管理工具竟然使用了将近1.5G的内存,吓死宝宝了。
于是反应给工具维护人员,很快进行了优化,释放了无用内存。但是总的内存占用量仍然有700+M,在切换几次环境之后,会稳定到这个值。因为我在公共配置里添加了Google的Hosts内容,所有触发了这个问题。
这个内存占用量,是特别的高,显然不是开发人员设定的值,而是OS X Java Plugin的默认设置。这种工具,内存占用应该只是在100M以下,最后能做20-30M最好。
在1处,切换了环境,可见同事优化的地方正在此,在切换环境的时候释放了内存,触发了Eden区的GC。
但在这之前,将近十分钟,内存使用量在稳步上升,竟然一次都没有触发GC,这就很奇怪了。应该是内存空间足够有关,程序刚开始运行,竟然就申请了1200+M的内存,真不知道系统咋想的。这和OS X JRE运行环境有关,应该是使用的默认的对内存大小限制。但是就目前的程序而言,显然这么多的内存有些浪费。
从曲线中可以看出来,堆内存的使用量最大也没超过200M,所以限定一下最大堆内存为200M,使用如下参数:
-Xmx200m
打开应用程序的目录,可以看见Info.plist文件,程序的启动参数就在这里面设置的,找到如下节点,添加堆内存最大限制-Xmx参数:
<key>JVMOptionskey>
<array>
<string>-Dapple.laf.useScreenMenuBar=truestring>
<string>-Xdock:name=HostsManagerstring>
<string>-Xmx200mstring>
array>
所有在OS X下的Java Application都可以在该文件中设置虚拟机参数。
观察堆内存情况,如下图所示:
自动触发了GC操作之前,内存使用量快并未达到60M。所有200M还是比较多余的,大概4分钟触发一次GC,回收了内存。
可以试试将堆内存最大设置为80M,看看情况,会不会发生堆内存溢出。修改Info.plist
<key>JVMOptionskey>
<array>
<string>-Dapple.laf.useScreenMenuBar=truestring>
<string>-Xdock:name=HostsManagerstring>
<string>-Xmx80mstring>
array>
从堆内存使用数据来看,在内存到25M作用时,就主动触发了GC,回收了内存,可见,应用程序真正使用的内存还不到30M,堆内存最大使用率还不足25%,实在浪费。如图所示:
最大堆内存,这次设置为40M,在观察一下效果:
这下堆内存的使用率能够达到一半以上了,最大40M,基本达到了预期效果。进一步压缩堆内存,还能进一步优化,会更加频繁的触发GC操作。
注意两幅图,截取的时间长度不同,所有不是GC越频繁,而是达到一定的内存的值,触发的GC操作。
最终设置堆内存大小为30M,总的内存使用量为110M作用,达到初期优化目标。
这时候,该使用我们按的差距VisualGC了,看一下总体的内存使用情况:
前面我们已经优化了堆内存,所有堆内存区域的数据还是比较理想的。使用VisualGC插件,内存使用情况更加清晰,优化目标也更明确。
数据说明:
Space:内存使用情况,实线格子是已经申请的内存,有颜色的部分是已使用内存,总体是内存区域的最大值。
Perm:永久代
Old:老年代
S0、S1、Eden: 新生代
老年代和新生代都属于Java堆内存(Heap),即-Xmx限制的区域。
最大内存:82+20+3+3+9 = 117M,接近于系统“活动监视器”中显示的内存值114.5M
Graphs:
Compile Time: 352 compiles 2.960s
运行时编译时间,对热点代码(HotSpot JDK默认虚拟机的名称由来),将字节码编译成本地二进制,编译了352次,共使用时间2.960s
Class Loader Time:
类加载时间,加载了2946个类,共花费719.913ms,有1个类未加载。
Eden Space (9.000M, 4.000M):3.444M 959 collections, 849.736ms。
9.000M 该区域最大内存,4.000M当前已经申请的内存,3.444M已经使用的内存,变化很快。也是GC最频繁的区域,幸存的对象,移入S0或者S1区域。该区域的GC为Moni GC,最频繁,也是速度最快的。老年代内存满了,会触发Full GC,会暂停所有用户进程值安全点,进行GC,速度比较慢。具体不在赘述,放JVM内存管理中详细叙述。
其他内存区域参数同理。
可以看到,新生代和老年代的使用率已经没有太大的空间可以优化了,但是永久代的内存82M,才使用了17M,使用率不足35%,可以大刀阔斧的,将其最大值设置为25M,六点余地,以免不够用。虽然才申请了21M,但是剩下的61M,也是化为了私有内存的,其他进程也是无法使用的。
最终优化参数如下:
<key>JVMOptionskey>
<array>
<string>-Dapple.laf.useScreenMenuBar=truestring>
<string>-Xdock:name=HostsManagerstring>
<string>-Xmx30mstring>
<string>-Xms30mstring>
<string>-Xmn10mstring>
<string>-XX:PermSize=25mstring>
<string>-XX:MaxPermSize=25mstring>
array>
可以看出来,内存总体使用情况还是可以进一步优化,但是空间并不大,也没这个必要了。优化完成后,总消耗内存控制在70M左右。基本达成目标。
PS:Java应用记得设置内存参数哦,OS X系统默认是个坑!