内部类返回参考保留的对象是什么意思

时间:2018-01-09 16:20:14

标签: java scala memory-leaks profiling yourkit

newbi使用profiler,我正在使用你的套件。 我在检查中看到可能的内存泄漏

Objects Retained by Inner Class Back References
Find objects retained via synthetic back reference of its inner classes.
Problem: Such objects are potential memory leaks.

这意味着什么?谁可以给出这样一个对象的好例子以及为什么这可能被视为泄漏? 感谢

1 个答案:

答案 0 :(得分:2)

很遗憾,您没有为此问题指定任何语言标记,因此我假设您的语言是Java。理解发生的事情的重要一点是回忆起Java支持nested aka inner classes,它可以是static而非static。此问题可能仅来自非static内部类。同样重要的是,Java中的所有匿名内部类都是非static,即使他们在技术上并不需要它。

考虑一些具有全局Scheduler服务的大型应用程序,该服务可以运行延迟或重复的ScheduledJob。像这样:

public interface ScheduledJob {
    boolean isRepetitive();

    long getDelay();

    void runJob();
}

class Scheduler {
    private final List<ScheduledJob> jobs = new ArrayList<>();

    public void registerJob(ScheduledJob job) {
        jobs.add(job);
    }

    public void runScheduler() {
       // some logic to run all registered jobs
    }
}

现在考虑你有一些插件系统和一个集成模块,它应该按照配置的时间间隔运行一次作业,并且配置存储在数据库中。

public interface Module {
    void register(Scheduler scheduler);
}

public class IntegrationModule implements Module {

    private java.sql.Connection db;

    private long readDelayConfiguration() {
        // read data from DB
    }

    public void register(Scheduler scheduler) {
        final long delay = readDelayConfiguration();

        scheduler.registerJob(new ScheduledJob() {
            @Override
            public boolean isRepetitive() {
                return true;
            }

            @Override
            public long getDelay() {
                return delay;
            }

            @Override
            public void runJob() {
                // do some integration stuff
            }
        });
    }
}

实际编译的代码是这样的:

class ScheduledJob_IntegrationModule_Nested implements ScheduledJob {
    private final IntegrationModule outerThis;
    private final long delay;

    public ScheduledJob_IntegrationModule_Nested(IntegrationModule outerThis, long delay) {
        this.outerThis = outerThis;
        this.delay = delay;
    }

    @Override
    public boolean isRepetitive() {
        return true;
    }

    @Override
    public long getDelay() {
        return delay;
    }

    @Override
    public void runJob() {
        // do some integration stuff
    }
}

public class IntegrationModule implements Module {

    // some other stuff
    ...

    public void register(Scheduler scheduler) {
        final long delay = readDelayConfiguration();
        scheduler.registerJob(new ScheduledJob_IntegrationModule_Nested(this, delay));
    }
}

现在,匿名子类ScheduledJob的一个实例捕获this的{​​{1}}。这意味着即使没有IntegrationModule的直接引用,全局IntegrationModule对象保留对Scheduler实例的引用这一事实意味着ScheduledJob_IntegrationModule_Nested所有IntegrationModule它的领域也永远有效地保留下来。这是纯粹的内存泄漏。

请注意,如果ScheduledJob_IntegrationModule_Nested是非匿名但非static嵌套类IntegrationModule,则情况相同。只有static嵌套类不会隐式捕获其&#34;所有者&#34;的实例。类。

如果你想象这是一个处理HTTP请求和处理程序有状态的Web应用程序,那么会有一个更复杂的例子。所以有一些&#34;调度员&#34;分析传入的HTTP请求,然后创建适当的处理程序的实例并将作业委托给它。这实际上是许多Web框架中的典型方法。

public abstract class StatefulRequestProcessor {
    protected final Scheduler scheduler;
    protected final HttpRequest request;

    public StatefulRequestProcessor(Scheduler scheduler, HttpRequest request) {
        this.scheduler = scheduler;
        this.request = request;
    }

    public abstract void process();
}

现在假设某些传入请求有一些延迟清理

public class MyStatefulRequestProcessor extends StatefulRequestProcessor {
    public MyStatefulRequestProcessor(Scheduler scheduler, HttpRequest request) {
        super(scheduler, request);
    }

    @Override
    public void process() {

        // do some processing and finally get some stored ID
        ...
        final long id = ...

        // register a clean up of that ID
        scheduler.registerJob(new ScheduledJob() {
            @Override
            public boolean isRepetitive() {
                return false;
            }

            @Override
            public long getDelay() {
                return 24 * 60 * 60 * 1000L; // one day later
            }

            @Override
            public void runJob() {
                // do some clean up
                cleanUp(id);
            }
        });
    }
}

现在这在技术上不是内存泄漏,因为大约24小时后scheduler将释放匿名ScheduledJob的实例,因此MyStatefulRequestProcessor也可用于垃圾回收。但是,这意味着在24小时内您必须在整个MyStatefulRequestProcessor内存储HttpRequest,包括HttpResponsedelegate等内容,即使在主要处理过程中技术上不需要它们结束。

对于C#,情况类似,除了通常你会有一个static来捕获它的父级而不是嵌套的类。

更新:该怎么做?

这不是一个很难的事实领域,更多的是基于意见的。

什么是内存泄漏?

这里的第一个问题是什么是&#34;内存泄漏&#34;?我认为有两个不同但相关的方面:

  1. 内存泄漏是程序的行为,表现为内存消耗的稳定且可能无限增长。这是一件坏事,因为这会降低性能并最终导致内存不足崩溃。

  2. 当某些内存区域(OOP世界中的对象)保留的时间比开发人员预期的长得多时,内存泄漏是程序的行为。

  3. 定义#1中描述的不良行为通常是#2中定义的错误的结果。

    做什么,内心邪恶?

    我认为YourKit警告你这些事情的原因是因为这种行为也可能对于程序员来说通常是不明显的,因为后向引用是隐式生成的,你很容易忘记这一点。此外,Java编译器不够智能,无法自行做出正确的决策,并且需要程序员通过明确指定(或避免)static关键字来做出决策。而且由于没有地方可以将IntegrationModule放在匿名的内部课程中,所以即使他们并不真正需要,他们也会抓住他们的父母。

    回答问题&#34;该怎么做?&#34;你首先应该理解为什么编译器会生成&#34; back reference&#34;。回到delay示例,可能有两种不同的行为:

    1. 我们希望从配置中读取delay一次并永久使用它(直到应用程序重启)
    2. 我们希望通过编辑配置(即无需重新启动)即时调整public class IntegrationModule implements Module { // some other stuff ... public void register(Scheduler scheduler) { final long delay = readDelayConfiguration(); scheduler.registerJob(new ScheduledJob_IntegrationModule_Nested(this, delay)); } static class IntegrationScheduledJob implements ScheduledJob { private final long delay; public IntegrationScheduledJob(long delay) { this.delay = delay; } @Override public boolean isRepetitive() { return true; } @Override public long getDelay() { return delay; } @Override public void runJob() { // do some integration stuff } } }
    3. 在第一种情况下,您可以将代码重写为

      static

      因此,您将匿名类命名为readDelayConfiguration并显式传递所有依赖项。

      在第二种情况下,我们实际上想要从getDelay致电this。这就是匿名对象需要外部类static的原因,因此编译器为我们提供了它。您仍然可以将您的匿名类转换为命名的Scheduler,并显式传递读取配置所需的所有依赖项,但新类必须保留所有依赖项,因此没有太大的好处。

      <强>生命周期

      另一个重点是不同物体的生命周期。如果非静态内部类的生命周期完全取决于其父对象的生命周期,或者最多只是未来的一小部分内容,则它们绝对可以。所以问题实际上是关于生命周期的差异。如果父对象具有无限生命周期(即全局保留),则一切正常。只有当这样的内部对象被泄露时,问题才会出现。通过一个短时间的对象进入&#34;外部世界&#34;具有扩展或可能的无限生命周期,例如在static示例中。我会说在这种情况下,当这是预期的行为时,你应该使用命名的this内部类并明确地传递外部{{1}},甚至可能写一些注释,以使它更清晰地用于像YouKit和其他开发人员认为这确实是被考虑过来而不是意外。