【问题标题】:Old Generation memory increases when using ThreadLocal使用 ThreadLocal 时老一代内存增加
【发布时间】:2017-09-28 11:18:43
【问题描述】:

我有 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。 CollectorThreadLocal 中初始化。

我还有一个多次执行的 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 会立即清除它对事件的所有引用,所以即使它没有被 GC,事件也应该是。

【问题讨论】:

    标签: java garbage-collection


    【解决方案1】:

    我说:

    我认为您可能在这里遇到了线程安全问题。

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

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

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

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

    【讨论】:

    • ThreadLocal 意味着只有一个线程可以访问该变量。所以每个线程都有自己的Collector 实例。不能有正确的竞争条件吗?
    • 是 Quartz 重用线程。但我验证了作业创建和 execute() 在同一个线程上工作。恢复收集器不是下一个工作的问题,因为在刷新时我清除了事件
    • 不过,我认为这是您问题的根源。老实说,线程本地并没有提高效率,flush() 这样创建线程对性能的影响很大。
    • 同意存在一些性能问题,但此作业每 3 分钟运行一次。但我真正追求的是对被转移到老一代的事件的可能解释
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-31
    • 2018-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多