Java 对局部变量的匿名类隐藏引用
我在Android应用程序中遇到了一个内存泄漏报告,经过一些调查后,我基本上找到了泄漏的位置,以下是简化代码:Java 对局部变量的匿名类隐藏引用,java,android,java-memory-leaks,Java,Android,Java Memory Leaks,我在Android应用程序中遇到了一个内存泄漏报告,经过一些调查后,我基本上找到了泄漏的位置,以下是简化代码: public class LeakTracker { public static List<Callback> callbacks = new ArrayList<>(); public static List<WeakReference<LeakingActivity>> weakList = new ArrayList
public class LeakTracker {
public static List<Callback> callbacks = new ArrayList<>();
public static List<WeakReference<LeakingActivity>> weakList = new ArrayList<>();
// causes leak of activity
public void startLeak(final LeakingActivity activity) {
callbacks.add(new Callback() {
// remove this line then no leak
Wrapper wrapper = new Wrapper(activity);
@Override
public void onCall() {
}
});
}
// no leak here
public void startLeak2(final LeakingActivity activity) {
weakList.add(new WeakReference<>(activity));
}
public interface Callback {
void onCall();
}
static class Wrapper {
private WeakReference<LeakingActivity> weakReference;
public Wrapper(final LeakingActivity activity) {
weakReference = new WeakReference<LeakingActivity>(activity);
}
}
}
公共类泄漏跟踪器{
public static List callbacks=new ArrayList();
public static List weakList=new ArrayList();
//导致活动泄漏
公共无效警报(最终泄漏活动){
add(新回调(){
//拆下此管路,然后确保无泄漏
包装器=新包装器(活动);
@凌驾
公用电话{
}
});
}
//这里没有漏洞
公共设施2(最终泄漏活动){
weakList.add(新的WeakReference(活动));
}
公共接口回调{
void onCall();
}
静态类包装器{
私人WeakReference WeakReference;
公共包装器(最终泄漏活动){
weakReference=新的weakReference(活动);
}
}
}
泄漏发生是因为我调用了一个函数“starteak”。活动变量将被泄漏。然而,如果我称之为“惊人的2”,泄漏就不会发生。我想知道为什么第一个案例会有漏洞。包装器也使用WeakReference
LeakActivity类占用大约3000万内存。在Android设备上呼叫Starteak大约5次会导致OOM。打电话给Strateakeak2是不行的。如果使用StartEak而不是StartEak2,则LeakCanary工具会报告泄漏。在您声明活动最终结果的第一个方法中。这将向
回调
实例添加活动引用,因此泄漏的不是包装
,而是回调
本身
另外,请记住,
Callback
是一个匿名的内部类,它还将包含对外部类LeakTracker
检查的引用(在调试器中)如果回调
的实例有一个私有字段,其中包含对活动的引用
。它表示回调中有这样一个引用:LeakTracker$1.val$activity。我想可能有。我不明白为什么编译器会在那里放一个引用。从我的人的角度看,没有明显的必要。而且去掉包装纸的东西会使参考消失。是的,那就是你的漏洞。有趣的是,我在“普通”Java中尝试过(我不是Android开发人员),并且在匿名类的实例中没有任何对局部变量的引用。我猜编译器认为Callback
需要活动,并创建隐藏字段来引用它。当变量直接用于匿名类时,我在“普通”Java中也看到了这一点。但在这种情况下不是这样。我的猜测是,与“普通”Java相比,Android编译器并没有那么优化。但如果您只是删除“Wrapper=newwrapper(activity);”,那么对activity的最终局部变量的引用就消失了。看起来这使得编译器添加了一个从回调到活动的隐藏引用则内部类回调
未使用任何活动实例。因此,它不会有对活动的隐藏引用。尝试删除该行,并在oncall()中使用活动实例。这也会使编译器将活动的隐藏引用添加到回调
,因此它会像您的示例一样泄漏。隐藏引用来自回调,而不是包装器。注意包装器是静态的,并且包装器没有保留对活动的强引用。回调具有对LeakTracker的隐藏强引用。但这里泄漏的是最终的局部变量活动。不是LeakTracker。@darklord引用不在回调的实现中
-如果这些是正常情况(不使用匿名类),那么是的,就不会有强引用。但在这种情况下,局部变量被捕获了,尽管我还没有测试来证明Drilon的理论,但我不会怀疑这是原因。