使用ThreadLocal时,旧生成内存会增加

时间:2017-09-28 11:18:44

标签: java garbage-collection

我有这样的课程CollectorThreadLocalScope

Collector {
    Collector() {
        events = new LinkedList<>();
    }

    add(Event e) {
        events.add(e);
    }

    flush() {
        LinkedList<Event> copy = events;
        new Thread(() -> {
            for (Event e : copy) {
                sendToServer(e);
            }
            copy.clear();
        ).start();

        events = new LinkedList<>();
    }
}

ThreadLocalScope {
    public static ThreadLocal<Collector> local = new ThreadLocal<>() {
        protected Collector initialValue() {
            return new Collector();
        }
    }
}

Collector只是添加事件,并且在调用flush时将这些事件发送到新线程中的API。 Collector初始化为ThreadLocal

我还有一个Job类,它被执行了几次(使用Quartz)。当这样定义时,一切都很好:

Job {
    execute() {
        for (int i = 0; i < 100,000; i++) {
            ThreadLocalScope.get().add(new Event());
        }
        ThreadLocalScope.get().flush();
    }
}

但是,如果我坚持像这样收集器:

Job {
    Collector collector;
    Job() {
        collector = ThreadLocalScope.get();
    }

    execute() {
        for (int i = 0; i < 100,000; i++) {
            collector.add(new Event());
        }
        collector.flush();
    }
}

我看到我的旧代内存使用量迅速增加,并且世界各地的垃圾收集周期一直在发生。唯一的区别是我已将Collector添加为成员变量,而不是每次都调用ThreadLocalScope.get()

这一增长只能意味着将事件转移到老一代。但为什么会这样呢? Collector会立即清除对事件的所有引用,因此即使它不是GCed,也应该是事件。

1 个答案:

答案 0 :(得分:0)

我说:

  

我认为你可能会遇到线程安全问题。

不正确的。我认为这比那更简单。

在第一个版本中,您在执行作业的线程的上下文中调用ThreadLocalScope.get()。

在第二个版本中,您在创建Job()对象的线程的contrext中调用ThreadLocalScope.get()。然后它在一个变量中被捕获,稍后在执行程序线程中使用。假设Job()对象都是在同一个线程上创建的,这意味着您的execute()方法共享同一个Collector对象。它们可能在不同的线程上运行。由于Collector不是线程安全的,因此存在危险。

还有一件事您可能不知道。 Quartz可能正在使用线程池。这意味着当execute()调用终止时,该线程将返回池中。下一次,如果Quartz使用相同的线程,它将重用上次的Collector对象。