java是一门面向对象语言。在java里,除了8种基本类型外,其他的都是对象。java的对象存放在堆(heap)上。每当你new一个对象,就会在堆上抢占一定的内存。java虚拟机的垃圾收集器(gc)一直在后台工作,当它确定某些对象不再被使用时,就会销毁它,回收堆上的内存。java垃圾收集器不是使用单纯的引用计数来确定一个对象是否已不再被使用,而是使用一种相对复杂的算法。这种算法也考量引用计数,但是还考量其他一些东西。据说这种算法能够判断出循环引用(就是A引用B,B引用C,C再引用A)。总之SUN是鼓吹这种算法要比其他算法优秀。我相信这种算法能够提高程序的稳定性。不过这种算法的一个很大的问题是内存回收效率很差。java的一道经典考题是这样的:
for (int i=0;i<10;i++) {
ClassA a = new ClassA();
}
问执行完以后,内存里有几个ClassA的对象实例。答案是10个。为什么,因为根据java神秘的垃圾回收算法,这些超出作用域的,不再有人引用的对象实例并不急着销毁。这在平时不会造成什么问题,但是如果在循环里创建了占用很大内存的对象,不能及时回收就会造成堆内存耗尽,溢出。java提供了System.gc()来让你建议虚拟机回收内存,但仅仅是建议,不是强制。
java虚拟机启动的时候会先向操作系统请求一小块内存作为堆内存。随着在堆里存放的数据(对象)越来越多,占用内存超过一定百分比的时候,java虚拟机就会向操作系统请求更多的内存分配给堆。我们在程序里可以使用Runtime.getRuntime().freeMemory()和Runtime.getRuntime().totalMemory()来获得当前堆的剩余内存和总内存。java虚拟机向操作系统请求的内存是不会归还的。还好它不会无限制地请求内存,有一个上限值。在jre1.5里,这个上限值缺省是64M。你可以通过虚拟机参数 -Xmx 来设置这个值。比如下面我设置这个值为640M:
java -Xmx640m org.formalin14.SampleClass
最近我在做批处理大量xml的时候,在每个循环里处理一个文件。每个循环里都要做很多运算和转换,同时也需要创建很多对象来保存中间数据。程序总是在处理了1000多个文件以后就报java.lang.OutOfMemoryError错误,是堆内存不够用了。我检查程序并确保循环里每个创建的对象在用完后都置为null(其实没有必要),并且优化了算法和程序结构,还在每次循环末尾加上System.gc()。但是在循环里总是不可避免地要创建新对象。而且,对于java虚拟机,你没有任何办法强制它销毁对象回收内存。这样,64M的堆内存绝对是不够它捣腾的。没办法只好把堆上限改到640M,总算够它捣腾了。
总的来说,平时还是应该养成良好的编程习惯,尽量节约内存和CPU,建议把堆上限调到8M,强制自己写高质量的代码。其他一些技巧比如避免读取整个文件到内存,读一点处理一点,然后就扔掉(祈祷扔掉以后能被赶快回收)。用SAX处理xml。用StringBuilder构造字符串。多用大量的数据做测试,能看出程序是否真的优秀。对于执行快的循环,末尾要加Thread.sleep(1),不然CPU一下就上去了。我前面的这个程序还是优化得不够,如果真有时间的话,相信任何程序都是可以优化得很好的。不过不管怎么努力,java程序的效率怎么也比不上用c写的程序。