Java对象资源清理之Cleaner

1,504 阅读3分钟

从上一篇文章中在读取jar的资源文件时,对于资源的清理工作时利用JDK提供的Cleaner工具,当Jar文件资源没有引用后需要关闭native资源。\

ZipFile的字段和构造函数

ZipFile的重要字段,其中一个重要字段就是CleanableResource,它表示Zip文件使用资源在使用完之后需要被清除的以下三类资源.

  • 需要关闭的inputStream.
  • 缓存的解压的对象。
  • zip文件native资源。
public class ZipFile implements ZipConstants, Closeable {
    private final String name;     // zip file name
    private volatile boolean closeRequested;

    // The "resource" used by this zip file that needs to be
    // cleaned after use.
    // a) the input streams that need to be closed
    // b) the list of cached Inflater objects
    // c) the "native" source of this zip file.
    private final @Stable CleanableResource res;
}

ZipFile的构造函数中主要是对res变量是创建CleanableResource对象

public ZipFile(File file, int mode, Charset charset) throws IOException
{
    String name = file.getPath();
    file = new File(name);
    this.name = name;
    this.res = new CleanableResource(this, ZipCoder.get(charset), file, mode);
}

CleanableResource构造函数

CleanableResource的构造函数如下: 主要创建四个个变量

  1. 创建Cleaner并调用register方法将zipFile对象和this注册到Cleaner中.
  2. 创建一个空的istreams的WeakHashSet集合赋值给istreams便令
  3. 创建双端数组ArrayDeque赋值给inflaterCache变量.
  4. 创建Source对象
CleanableResource(ZipFile zf, ZipCoder zc, File file, int mode) throws IOException {
    this.cleanable = CleanerFactory.cleaner().register(zf, this);
    this.istreams = Collections.newSetFromMap(new WeakHashMap<>());
    this.inflaterCache = new ArrayDeque<>();
    this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0, zc);
}

Cleaner的create函数

  1. 创建一个Cleaner对象。
  2. 获取cleaner中impl字段并调用其start方法.
public static Cleaner create(ThreadFactory threadFactory) {
    Objects.requireNonNull(threadFactory, "threadFactory");
    Cleaner cleaner = new Cleaner();
    cleaner.impl.start(cleaner, threadFactory);
    return cleaner;
}

Cleaner构造函数中初始化时创建一个CleanerImpl对象,

private Cleaner() {
    impl = new CleanerImpl();
}

CleanerImpl的构造函数

主要创建ReferenceQueue,PhantomCleanableRef两个类型的变量.

public CleanerImpl() {
    queue = new ReferenceQueue<>();
    phantomCleanableList = new PhantomCleanableRef();
}

CleanerImpl的start函数

  1. 为Cleaner对象创建虚引用.
  2. 利用线程工厂创建Thread对象.并设置为Daemon为true.
  3. 调用线程的启动start启动线程.
public void start(Cleaner cleaner, ThreadFactory threadFactory) {
    new CleanerCleanable(cleaner);
    if (threadFactory == null) {
        threadFactory = CleanerImpl.InnocuousThreadFactory.factory();
    }
    Thread thread = threadFactory.newThread(this);
    thread.setDaemon(true);
    thread.start();
}

CleanerImpl的run方法

这个Runnable就上CleanerImpl的start启动的后台线程的执行的run方法体。

  1. 获取当前运行的线程.判断是否是InnocuousThread类型,如果是则返回,否则返回null。
  2. 开始while循环,条件是phantomCleanableList不为空,PhantomCleanable是需要清理的动作的虚引用对象。
    2.1 如果Thead对象不为空,则先清除TheadLocals变量的值。
    2.2 从ReferenceQueue队列中删除一个元素,超时时间是60秒,如果取Cleanable对 象,并执行其clean方法,(这里是由GC回收虚引用对象PhantomCleanableRef后会将会后引用对象放入ReferenceQueue)
CleanerImpl implements Runable {
@Override
public void run() {
    Thread t = Thread.currentThread();
    InnocuousThread mlThread = (t instanceof InnocuousThread)
            ? (InnocuousThread) t
            : null;
    while (!phantomCleanableList.isListEmpty()) {
        if (mlThread != null) {
            // Clear the thread locals
            mlThread.eraseThreadLocals();
        }
        try {
            // Wait for a Ref, with a timeout to avoid getting hung
            // due to a race with clear/clean
            Cleanable ref = (Cleanable) queue.remove(60 * 1000L);
            if (ref != null) {
                ref.clean();
            }
        } catch (Throwable e) {
            // ignore exceptions from the cleanup action
            // (including interruption of cleanup thread)
        }
    }

Cleaner的register函数

Cleaner的注册主要创建一个幻引用PhantomCleanableRef,传入之前的Zipfile对象,Cleaner对象自己以及Runnable的对象(实际就是上面分下的CleanableResource对象),

public Cleanable register(Object obj, Runnable action) {
    return new CleanerImpl.PhantomCleanableRef(obj, this, action);
}

PhantomCleanableRef的字段和构造函数

1.初始化父类传入对象引用和Cleaner。 2.赋值action为传入的Runnable对象(这里实际就是CleanableResource对象)

public static final class PhantomCleanableRef extends PhantomCleanable<Object> {
    private final Runnable action;
public PhantomCleanableRef(Object obj, Cleaner cleaner, Runnable action) {
    super(obj, cleaner);
    this.action = action;
}

PhantomCleanable的构造方法

  1. 初始化父类传入虚引用对象以及referenceQueue队列.
  2. CleanerImpl中phantomCleanableList变量赋值给list变量。
  3. 将自己insert到phantomCleanableList双向链表中头部.
public PhantomCleanable(T referent, Cleaner cleaner) {
    super(Objects.requireNonNull(referent), CleanerImpl.getCleanerImpl(cleaner).queue);
    this.list = CleanerImpl.getCleanerImpl(cleaner).phantomCleanableList;
    insert();
}

PhantomCleanableRef的clean方法

  1. 首先从phantomCleanableList双向链表中删除自己
  2. 执行父类的clear方法.
  3. 执行performCleanup方法。
public final void clean() {
    if (remove()) {
        super.clear();
        performCleanup();
    }
}

PhantomCleanableRef的performCleanup方法

实际就是执行构造方法中传入Runnable对象的run方法(实际传入是CleanableResource对象)

@Override
protected void performCleanup() {
    action.run();
}

CleanableResource的run方法

  1. 释放解压的缓存资源.
  2. 关闭流对象。
  3. 释放JVM的native的source资源.
private static class CleanableResource implements Runnable
  //省略
 public void run() {
        IOException ioe = null;

        // Release cached inflaters and close the cache first
        Deque<Inflater> inflaters = this.inflaterCache;
        if (inflaters != null) {
            synchronized (inflaters) {
                // no need to double-check as only one thread gets a
                // chance to execute run() (Cleaner guarantee)...
                Inflater inf;
                while ((inf = inflaters.poll()) != null) {
                    inf.end();
                }
                // close inflaters cache
                this.inflaterCache = null;
            }
        }

        // Close streams, release their inflaters
        if (istreams != null) {
            synchronized (istreams) {
                if (!istreams.isEmpty()) {
                    InputStream[] copy = istreams.toArray(new InputStream[0]);
                    istreams.clear();
                    for (InputStream is : copy) {
                        try {
                            is.close();
                        } catch (IOException e) {
                            if (ioe == null) ioe = e;
                            else ioe.addSuppressed(e);
                        }
                    }
                }
            }
        }

        // Release zip src
        if (zsrc != null) {
            synchronized (zsrc) {
                try {
                    Source.release(zsrc);
                    zsrc = null;
                } catch (IOException e) {
                    if (ioe == null) ioe = e;
                    else ioe.addSuppressed(e);
                }
            }
        }
        if (ioe != null) {
            throw new UncheckedIOException(ioe);
        }
    }
}

总结
本文主要对Jar的文件资源的回收的分析,其主要利用利用创建虚引用并传入ReferenceQueue,然后ClearnerImpl开启一个后台线程,循环从ReferenceQueue取出虚引用对象,并执行其clean方法,然后回调传入Runnable的run方法,进行相应的资源清理工作。