获取java程序运行时内存信息
由于最近想自己动手测试一下String和StringBuffer的效率问题,需要获取程序运行时的内存占中信息,于是上网查了一下,根据查到的资料写了个程序,发现结果有问题,才发现查到的资料是错误的.所以在这里跟大家分享一下获取内存占用的正确方法
错误的方法
//程序开始时:(先调用一下垃圾回收,但是不一定立即执行)
Runtime.getRuntime().gc();
long initm=Runtime.getRuntime().freeMemory();
//程序结束时:
Runtime.getRuntime().gc();
long endm=Runtime.getRuntime().freeMemory();
//计算空闲差:
System.out.println(initm-endm);
关于gc
方法
首先,在最开始调用Runtime.getRuntime().gc()
确实正确,但是注释中说
先调用一下垃圾回收,但是不一定立即执行
这句话就有问题了,java api文档中对gc方法的描述是这样的:
调用
gc
方法暗示着 Java 虚拟机做了一些努力来回收未用对象,以便能够快速地重用这些对象当前占用的内存。当控制权从方法调用中返回时,虚拟机已经尽最大努力从所有丢弃的对象中回收了空间。
也就是说,调用gc
时java虚拟机一定会进行垃圾回收.那么,为什么网上甚至许多教科书上说gc
方法并不一定会立即执行呢?个人猜测可能是运行完gc
方法后,内存的消耗量可能并没有显著的减少,因为这个时候可能并没有多少可以回收的垃圾.而过了一段时间后jvm又回收了一次内存,这次内存的消耗量明显的减少了.于是好多地方都会说gc
方法并不一定会立即执行.
而在第二次获取”内存”时,是不能调用gc
方法的,因为这样做会把一些中间的临时变量回收掉,导致获取到的”内存”并不一定是运行过程中真正使用的”内存”量.
关于freeMemory
方法
那么使用freeMemory
方法获取前后两次的空闲内存,然后相减得到的不就应该是执行时消耗的内存吗?
如果内存总量不变,那么这种方法确实可以:
内存总量1 = 开始时空闲内存 + 开始时使用内存
内存总量2 = 结束时空闲内存 + 结束时使用内存
内存总量1 = 内存总量2
可以推出下面的式子:
开始时空闲内存 - 结束时空闲内存
= 内存总量1 - 开始时使用内存 - (内存总量2 - 结束时使用内存)
= 结束时使用内存 - 开始时使用内存
但是java api文档中关于totalMemory
方法的描述是:
返回 Java 虚拟机中的内存总量。此方法返回的值可能随时间的推移而变化,这取决于主机环境。
这句话是说内存总量是可能变化的, 也就是说,不能使用开始时空闲内存-结束时空闲内存
得到结束时使用内存 - 开始时使用内存
,也就是内存使用量.
那么,我们该如果获取运行时内存使用量呢?请继续往下看.
在java程序中获取内存使用量
我们先说说在java程序中获取运行时内存,上代码:
Runtime run = Runtime.getRuntime();
System.in.read(); // 暂停程序执行
// System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + (run.totalMemory()-run.freeMemory()) );
run.gc();
System.out.println("time: " + (new Date()));
// 获取开始时内存使用量
long startMem = run.totalMemory()-run.freeMemory();
System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + startMem );
String str = "";
for(int i=0; i<50000; ++i){
str += i;
}
System.out.println("time: " + (new Date()));
long endMem = run.totalMemory()-run.freeMemory();
System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + endMem );
System.out.println("memory difference:" + (endMem-startMem));
/*
run.gc();
System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + (run.totalMemory()-run.freeMemory()) );
*/
代码中没有过于复杂的逻辑,关于调用System.in.read
方法暂停程序以及打印当前时间的原因我们到后面再解释.
这个程序在我的电脑上的一次输出为:
由于内存总量增长了5倍(内存总量就是在 任务管理器 中看到的内存占中量),导致空闲内存也增长了,那么开始时空闲内存 - 结束时空闲内存
就是负的…
这不排除是垃圾回收的结果,各位可以多执行几次看看结果:空闲内存确实是增长了.当然,各位也可以把代码的那几行注释取消了,看看执行过垃圾回收以后的内存变化.
在代码中获取程序内存占用量的优点:
- 不需要其他任何工具,就可以大致测出我们程序消耗的内存.
缺点是
- 可能jvm刚回收过垃圾,我们的程序就获取了内存的使用量,导致结果严重偏低.不过这种概率还是很低的.
- 需要改动我们的程序.测试完成以后还需要删除这些代码,不小心可能引入bug.这才是最大的问题!
使用工具
使用性能分析工具可以很方便的获取程序内存,cpu占用等信息.同时也不需要修改程序.java的性能分析工具还是不少的,我们这里使用的是jvisualvm.它最大的优点就是JDK自带,安装过JDK以后系统里就有jvisualvm了.我们可以在%JAVA_HOME%/bin
目录中找到jvisualvm.exe
,这个程序使用相当简单,我们就不说使用方法了,唯一需要注意的就是visualvm”打开”一个程序需要一段时间,如果在”打开”程序时程序结束了,那么我们什么信息也得不到,所以我们需要在程序开始出暂停程序.这就是我们在最开始处调用System.in.read
方法的原因.
打开visualvm,然后运行我们的程序,在VisualVM中双击打开我们的程序进行分析.在监视标签中可以查看内存使用量等信息.下面这幅图和上面的运行结果相对应:
我们可以拿着张图和我们的程序输出结果中对应的时间点的内存占信息用进行比对.来获取更真实的内存占用量.
visualvm应该是1秒获取一次内存使用量,而不是取这1秒的平均值,作为这1秒的内存占用量,不然的话,上面那幅图以及下面这几幅图是没法解释的:
|
|
最后再说一句,如果你的visualvm安装了Visual GC插件,那么还可以获取到垃圾回收的一些信息,比如垃圾回收次数及大致频率:
从图片可以看出, 共回收了149次垃圾,在后面一段时间,也就是我们的那个循环,垃圾回收频率很高.
OK,关于内存分析就说到这吧.如果有什么地方说错了,还请指教.
写于 2015/05/15