很多人都认为Java的内存管理机制使用的是引用计数这样的方式来确定一个对象是否存活:大致意思就是,为每个对象设定一个引用计数,当该对象被引用则该计数+1,解除引用的时候该计数-1,则当计数为0时则该对象即可被释放,否则认为该对象不能被清理。
然而,遇到这样的问题又会怎么样呢?
/**
* Using java -Xmx8M -Xms8m OutOfMemory
* to run this demo
*/
class OutOfMemory{
private static int SIZE_1MB = 1024*1024;
private byte[] data = new byte[SIZE_1MB];
public static void main(String args[]){
int i;
Object objs[] = new Object[100];
for(i=0;i<100;i++){
objs[i] = new OutOfMemory();
}
}
}
通过为java命令设定参数,设定堆大小为8m,那么,很明显,这个程序会因为内存不足而挂掉。
这便是Java堆中内存不足,而现有的对象均不可被GC清理(因为每个对象实例的引用都还没解除,也就是引用计数不为0),故因内存不足而死掉。
而如果把程序改成这样:
/**
* Using java -Xmx8M -Xms8m OutOfMemory2
* to run this demo
*/
class OutOfMemory2{
private static int SIZE_1MB = 1024*1024;
private byte[] data = new byte[SIZE_1MB];
public static void main(String args[]){
int i;
Object obj;
for(i=0;i<100;i++){
obj = new OutOfMemory();
}
}
}
而遇到这样一个程序 的时候,情况就不一样了:
/**
* @author hackeris
* Using java -Xmx8M -Xms8m IsGCUsingReferenceCounting
* to run this demo
*/
public class IsGCUsingReferenceCounting {
public Object referenceOfAnotherObject = null;
private static final int SIZE_1MB = 1024 * 1024;
private byte[] data = new byte[SIZE_1MB];
public static void main(String args[]) {
int i;
for (i = 0; i < 100; i++) {
testGC();
// System.gc();
}
}
public static void testGC() {
IsGCUsingReferenceCounting obj1 = new IsGCUsingReferenceCounting();
IsGCUsingReferenceCounting obj2 = new IsGCUsingReferenceCounting();
obj1.referenceOfAnotherObject = obj2;
obj2.referenceOfAnotherObject = obj1;
}
}
如果GC是通过引用计数来确定对象是否存活,那么这个程序就无法正常的结束,必然会因为内存不足而挂掉。在testGC函数中,obj1持有obj2的引用,obj2持有obj1的引用,因此两个对象的引用计数均不为0,故在函数testGC退出的时候,它们由于相互引用,其引用计数均不会减少,那么这样的情况下,GC便无法将其清理,使得最终程序会因为内存不足而挂掉。
但我们实际运行这个程序的时候却一切风平浪静,GC能够应对这样的相互引用的情况,可以看出,GC并非使用单纯的引用计数来确定一个对象是否存活,而是采用了更加高明的策略。
笔者使用的Java环境如下:
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)