Skip to content

Latest commit

 

History

History
126 lines (88 loc) · 7.04 KB

File metadata and controls

126 lines (88 loc) · 7.04 KB
# 3.6 内存操作相关的API

本节将对Java编程语言中与内存管理相关的内容进行介绍。

在Java中,妄图自己操作内存,脱离垃圾回收器的管理(例如即使对象已经变成垃圾,但坚持不回收之),绝对是自讨苦吃。但Java仍然提供了相应的机制来 提醒垃圾回收器进行工作,这其中有利有弊,在使用的时候需要特别注意。

## 3.6.1 析构函数

从Java 1.0起,每个Java对象都包含一个finalize方法,用户可以覆盖该方法以完成自定义析构操作。按照javadoc的说明,finalize方法会在该对象被当作垃圾回收之前调用,单纯这样看的话,将之作为对象的析构函数是个不错的主意。

但事实上没这样简单,由于finalize方法中可能会包含任何代码,因此有可能有一些错误代码,是finalize方法处理问题,例如,在finalize方法中是已经成为垃圾的对象 复活,或者根据垃圾对象克隆了一个新对象,这样,垃圾回收器就不会再将之回收了。此外,如果在finalize方法中释放系统资源(例如释放文件句柄),则可能会导致资源无法被充分利用,因为无法保证析构函数会在何时被执行。因此,对系统资源的释放应该由程序员显式控制。

进一步讲,finalize可能会被任意线程在任意时间调用,无论该线程当前持有哪个对象的锁,都可以。这非常危险,可能会导致死锁的出现,还有可能会违反Java自身的语义。

"Java中的析构函数的设计就是一个失误,应避免使用",这不仅仅是本书作者的意见,也是Java社区的一致意见。

## 3.6.2 Java中的引用

很多人以为Java中只有一种引用,对象只分为可达和不可达两种,后一种会被垃圾回收器处理掉。但事实上,Java中存在有多种引用,可以认为是对处于不同存活程度的对象的描述。普通对象的引用称为 强引用(strong reference)

几种对象引用类型位于java.lang.ref包下,均继承自java.lang.ref.Reference类。所有的引用类都有一个get方法用于获取引用所指向的实际对象,如果对象是不可达的,例如已经被回收了,则get方法返回null

当对象的可达性发生变化时会被放入到java.lang.ref.ReferenceQueue类的实例中,例如引用对象要被回收掉的时候。在创建引用对象时,可以将之与某个java.lang.ref.ReferenceQueue的实例绑定,通过对ReferenceQueue实例的轮询,可以得知对象会在何时被回收掉。

Java中有4类主要的引用,即 强引用,弱引用,软引用和虚引用(strong, weak, soft and phantom references)

### 3.6.2.1 弱引用

弱引用指向些不足以使自己保留在内存中的引用,实际上,java.lang.ref.WeakReference类是对强引用的一个包装,将其标明为弱引用:

WeakReference weak = new WeakReference(object);

在上面的示例中,弱引用实际指向的对象可以用weak.get()得到,由于object可能会在任意被回收掉,所以weak.get()方法可能返回null

弱引用常作为缓存的key用于java.util.WeakHashMap实例中。当JVM执行垃圾回收时,弱引用所指向的对象会被释放掉,这样可以防止无意识的内存泄漏。

### 3.6.2.2 软引用

相对于弱引用来说,软引用所指向的对象,垃圾回收器会尽可能将之保存在内存中,但当内存不足时,会首先将之回收掉。

软引用到底比弱引用"强"多少,取决于JVM的具体实现。理论上,软引用可以实现得与弱引用一样,并不违反Java的语义。

### 3.6.2.3 虚引用

虚引用更常用于实现析构函数,用于取代finalize方法,与弱引用和软引用类似,它也是对普通对象的包装,只不过其get方法永远都返回null

访问虚引用需要通过轮询java.lang.ref.ReferenceQueue实例,以便得知某个对象是否要被回收掉了。由于虚引用的get只会返回null,所以无法获取到其所指向的真正对象,也就不会出现像finalize方法那样的问题了。

下面的示例代码使用finalize方法做析构函数:

/**
 * Prints the number of finalized objects
 */
public class Finalize {
    static class TestObject {
        static int nObjectsFinalized = 0;
        
        protected void finalize() throws Throwable {
            System.err.println(++nObjectsFinalized);
        }
        
    }

    public static void main(String[] args) {
        for (;;) {
            TestObject o = new TestObject();
            doStuff(o);
            o = null;    //clear any remaining refs to "o"
            System.gc(); //try to force gc
        }
    }
}

下面的示例代码使用虚引用实现同样的目标:

/**
* Prints the number of finalized objects using PhantomReferences
*/
import java.lang.ref.*;
public class Finalize {
    static class TestObject {
        static int nObjectsFinalized = 0;
    }
    
    static ReferenceQueue<TestObject> q = new ReferenceQueue<TestObject>();

    public static void main(String[] args) {
        Thread finalizerThread = new Thread() {
            public void run() {
                for (;;) {
                    try {
                        //block until PhantomReference is available
                        Reference ref = q.remove();
                        System.err.println(++TestObject.nObjectsFinalized);
                    } catch (InterruptedException e) {
                    }
                }
            }
        };
        finalizerThread.start();
        for (;;) {
            TestObject o = new TestObject();
            PhantomReference<TestObject> pr = new PhantomReference<TestObject>(o, q);
            doStuff(o);
            o = null; //clear any remaining refs to "o"
            System.gc(); //try to force GC
        }
    }
}
## 3.6.3 JVM的行为差异

对于JVM来说,一定谨记,编程语言只能 提醒垃圾回收器工作。就Java而言,在设计上,它本身并不能精确控制内存系统。例如,假设两个JVM厂商所实现软引用在缓存中具有相同的存活时间,这根本就是不现实的。

另外一个问题就是大量用户对System.gc()方法的错误使用。System.gc()方法也是仅仅 提醒运行时"现在可以做垃圾回收了"。在某些JVM实现中,对该方法的频繁调用,导致频繁的垃圾回收操作,而在某些JVM实现中,大部分时间是忽略该调用的。

在以往的工作经验中,多次看到该方法被滥用,很多时候,只是去除该调用就可以大幅提升性能,这也是为什么JRockit中会有-XX:AllowSystemGC=False这个参数。