Java提高——Java的内存回收(1)
Java引用的种类
当程序员通过new关键字创建对象,即视为为Java对象申请内存空间。JVM会在堆内存中为每个对象分配空间;当一个对象失去引用的时候,JVM垃圾回收机制会自动清除他们,并回收他们所占用的内存空间。
Java内存管理包括内存分配(创建Java对象的时候)和内存回收(回收 Java对象的时候)两个方面。
JVM垃圾回收机制是否回收一个对象的标准:是否有引用变量引用该对象?
class Nod{ Nod next; String name; public Nod(String name) { this.name = name; } } public class NodeTest { public static void main(String[] args) { Nod n1 = new Nod("第一个节点"); Nod n2 = new Nod("第二个节点"); Nod n3 = new Nod("第三个节点"); n1.next = n2; n2 = null; n3 = n2; } }
JVM的垃圾回收机制采用有向图的方式来管理内存中的对象。程序的有向图:
有向图现实只有“节点3”处于不可达状态,因此JVM的垃圾回收机制将会回收它。
对象在堆内存中对应的有向图的状态:可达状态、可恢复状态、不可达状态
Java语言对对象的引用方式:强引用、软引用、弱引用、虚引用
强引用:程序创建一个对象,并将对象赋给一个引用变量,这就是强引用。当对象被一个及以上的强引用变量引用时,它处于可达状态,不可被垃圾回收机制回收。处于强引用的对象绝对不会被垃圾回收机制回收,即使内存紧张。由于JVM不会回收强引用所引用的Java对象,因此强引用是造成内存泄漏的主要原因。
软引用:当一个对象只有软引用的时候,可能会被垃圾回收机制回收——当系统内存空间足够的时候不会回收,系统内存空间不足的时候会回收。软引用常用于对内存敏感的程序中。
class Person{ int age; String name; public Person( String name,int age) { this.age = age; this.name = name; } @Override public String toString() { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}'; } } public class SoftReferenceTest { public static void main(String[] args) { SoftReference<Person>[] person = new SoftReference[100]; for (int i = 0; i < person.length; i++) { person[i] = new SoftReference<Person>(new Person("名字"+i,(i+1)*4%100)); } System.out.println(person[2].get()); System.out.println(person[4].get()); //通知系统进行垃圾回收 System.gc(); System.runFinalization();//强制调用已经失去引用的对象的finalize方法 //垃圾回收机制运行后,SoftReference的元素数组保持不变 System.out.println(person[2].get()); System.out.println(person[4].get()); } }
弱引用:弱引用与软引用类似,区别在于弱引用所引用对象的生存期更短。弱引用的级别更低。对于弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。
public class WeakReferenceTest { public static void main(String[] args) { //创建字符串对象 String str = new String("java"); //创建WeakReference对象 WeakReference<String> weakReference = new WeakReference<String>(str); //切断weakReference和java对象之间的引用 str = null; //取出弱引用所引用的对象 System.out.println(weakReference.get()); //强制垃圾回收 System.gc(); System.runFinalization(); //再次取出弱引用所引用的对象 System.out.println(weakReference.get()); } }
弱引用具有很大的不确定性,因为每次回收机制执行的时候都会回收弱引用对象,而垃圾回收机制又不受程序员控制,因此程序获取弱引用的Java对象时,必须小心空指针异常——通过弱引用获取的Java对象可能为null。
与WeakReference功能类似的还有WeakHashMap,当有大量Java对象需要使用弱引用时,可以考虑WeakHashMap
class CrazyKey{ String name; public CrazyKey(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CrazyKey crazyKey = (CrazyKey) o; return Objects.equals(name, crazyKey.name); } @Override public int hashCode() { return Objects.hash(name); } @Override public String toString() { return "CrazyKey{" + "name='" + name + '\'' + '}'; } } public class WeakHashMapTest { public static void main(String[] args) throws InterruptedException { WeakHashMap<CrazyKey,String> weakHashMap = new WeakHashMap<CrazyKey, String>(); //循坏放入10个key-value for (int i = 0; i < 10; i++) { weakHashMap.put(new CrazyKey(i+1+""),"value"+(i+1)); } //垃圾回收之前weakHashMap与普通的hashMap没有什么区别 System.out.println(weakHashMap); System.out.println(weakHashMap.get(new CrazyKey("2"))); //垃圾回收 System.gc(); //让线程休眠 Thread.sleep(50); //垃圾回收后weakHashMap里的所有内容被清空 System.out.println(weakHashMap); System.out.println(weakHashMap.get(new CrazyKey("2"))); } }
虚引用:软引用和弱引用可以单独使用,虚引用不能单独使用,必须和引用队列联合使用,虚引用的主要作用就是跟踪对象被垃圾回收的状态。程序可以通过检查与虚引用关联的引用队列中是否包含指定的虚引用,从而了解虚引用所引用的对象是否被回收。
引用队列由java.lang.ref.ReferenceQueue类表示,用于保存被回收后对象的引用,当把软引用、弱引用和引用队列联合使用的时候,系统回收被引用的对象之后,将会把被回收的对象的引用添加到关联的引用队列之中。与软引用和弱引用不同的是,虚引用在对象被释放之前放进了队列之中,这使得可以在对象被回收之前采取行动。
public class PhantomReferenceTest { public static void main(String[] args) { //字符串对象 String str = new String("java"); //创建引用队列对象 ReferenceQueue<String> rq = new ReferenceQueue<String>(); //创建虚引用对象,让虚引用引用“java”对象 PhantomReference<String> pr = new PhantomReference<String>(str,rq); //切断str和“java”的引用关系 str =null; //试图取出虚引用所引用的对象,但程序并不能通过虚引用访问被引用的对象,因此为null System.out.println(pr.get()); //强制垃圾回收 System.gc(); System.runFinalization(); //取出引用队列中最先进入队列中引用与pr进行比较 System.out.println(rq.poll() == pr); } }
要使用这些特殊的引用类,就不能保留对对象的强引用。否则就会浪费这些引用所带来的好处。