JAVA的内存回收
Java内存管理包括内存分配和内存管理两方面,这两方面都是JVM自动完成的,带来了很大方便的同时也加重了JVM的工作,从而使Java程序运行变慢。
基本上可以把JVM内存中的对象引用理解成一种有向图,把引用变量、对象都当成有向图的顶点,将引用关系当成图的有向边,有向边总是从引用端指向被引用对象。因为对象都是由线程创建的,所以可以把线程对象当成有向图的起始顶点。
当一个堆对象在内存中运行时可以分为三种状态:
①可达状态:当一个对象被创建后,有一个及以上的变量引用它。在有向图中可从起始顶点导航到该对象,那么它就处于可达状态,程序可以通过引用变量来调用对象的方法和属性。
②可恢复状态:如果某个对象不再有任何引用变量引用它,它将先进入可恢复状态,系统的垃圾回收机制准备回收该对象所占用的内存。在回收之前,系统会调用可恢复对象的finalize方法进行资源清理,如果系统在调用finalize方法重新让一个及以上的引用变量引用该对象,则这个对象会再次变为可达状态。
③不可达状态:当对象的所有关联都被切断,且系统调用所有对象的finalize方法依然没有使该对象变为可达状态,那么这个对象将永久失去引用,被系统回收。
对象状态转换
为了更好地管理对象的引用,从JDK1.2开始Java在java.lang.ref包下提供了3个类:SoftReference、PhantomReference、WeakReference,分别代表对象的三种引用方式:软引用、弱引用、虚引用。
归纳起来Java有四种引用方式:强引用、软引用、弱引用、虚引用。
①强引用:这是Java程序中最常见的引用方式,程序创建一个对象并把这个对象赋给一个引用变量,这个变量就是强引用。被强引用的对象不会被垃圾回收机制回收,不论内存有多么紧张,即使有些Java对象以后永远不会用到。由于JVM肯定不会回收强引用的Java对象,因此强引用是造成Java内存泄漏的主要原因之一。
②软引用:软引用需要通过SoftReference类来实现,当一个对象只具有软引用时,它可能被垃圾回收机制强制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也使用该对象。当系统内存不足时,系统会回收它。
import java.lang.ref.SoftReference; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
public class SoftReferenceTest { public static void main(String[] args) { SoftReference<Person>[] people = new SoftReference[100]; for (int i = 0; i < people.length; i++) { people[i] = new SoftReference<Person>(new Person("" + i, (i + 1) * 4 % 100)); } System.out.println(people[2].get()); System.out.println(people[4].get()); //通知系统进行垃圾回收 System.gc(); System.runFinalization(); //垃圾回收之后,SoftReference数组的元素保持不变 System.out.println(people[2].get()); System.out.println(people[4].get()); } }
上面程序创建了一个长度为100的SoftReference数组,程序使用这个数组来保存100个Person对象,当系统内存足够时,即使系统进行垃圾回收,也不会回收这些对象的内存空间。
如果将SoftReference数组的长度改为10000,当使用java -Xmx2m -Xms2m SoftReference命令强制堆栈内存只有2M,这样将使得系统内存紧张。在这种情况下,软引用所引用的Java对象将会被垃圾回收机制回收。
③弱引用:弱引用与软引用有点类似,区别在于弱引用所引用对象的生存期更短。弱引用通过WeakReference类实现,当系统垃圾回收机制运行时,不论系统内存是否足够,总会回收该对象所占用的内存。不是立即回收,而是等到系统垃圾回收机制运行时才会被回收。
import java.lang.ref.WeakReference; public class WeakReferenceTest { public static void main(String[] args) { //创建一个字符串对象 String str = new String("WeakReferenceTestString"); //创建一个弱引用 WeakReference<String> weakReference = new WeakReference<String>(str); //切断str与"WeakReferenceTestString"之间的引用 str = null; //取出弱引用所引用的对象 System.out.println(weakReference.get()); //强制垃圾回收 System.gc(); System.runFinalization(); //再次取出弱引用的对象 System.out.println(weakReference.get()); } }
上面的程序创建了一个“WeakReferenceTestString”字符串对象,并让str引用变量引用它,然后创建一个弱引用对象,并让这个弱引用对象和str指向同一个对象。当程序执行str=null时切断了 str 与“WeakReferenceTestString”字符串对象之间的引用关系,此时只有weakReference弱引用对象引用这个字符串对象,程序依然可以通过weakReference来访问该字符串对象。但是如果系统垃圾回收机制启动,只有弱引用的对象将会被清理掉。
弱引用具有很大的不确定性,因为每次垃圾回收机制执行时都会回收弱引用所引用的对象,而垃圾回收机制又不受程序员控制。由于这种不确定性,当程序希望从弱引用取出对象时,可能这个对象已经被释放了,这时需要重新创建该对象。
与WeakReference功能类似的还有WeakHashMap,当程序有大量的对象需要用弱引用来引用时,可以考虑使用WeakHashMap来保存他们。
④虚引用:虚引用不能单独使用,主要作用是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队列中是否已包含指定的虚引用,从而了解引用的对象是否即将被回收。引用队列由ReferenceQuene类表示,它用于保存被回收后对象的引用。当软引用、弱引用与引用队列联合使用时,软引用和弱引用在被释放之后,将会把它对应的引用添加到关联的引用队列之中,耳虚引用是在被释放之前,将把它对应的虚引用添加到它关联的引用队列之中,这可使得对象在被释放之前采取行动。