Fundamentals 9 min read

Anonymous Inner Classes vs Lambda Expressions: Memory Leak Risks in Java

This article examines how anonymous inner classes and lambda expressions differ in their handling of outer class references, analyzes compiled bytecode to reveal potential memory leak risks, and demonstrates how explicit references affect lambda behavior in Java Android development.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Anonymous Inner Classes vs Lambda Expressions: Memory Leak Risks in Java

Background

Anonymous inner classes hold a reference to their enclosing class, which can cause memory leaks; the article investigates whether lambda expressions exhibit the same risk.

Anonymous Inner Class vs Lambda Expression

A sample class TestInner is created with two methods: test (containing a lambda) and test1 (containing an anonymous inner class). The source code is:

public class TestInner {
    public void test(){
        new Thread(()->{
            Log.i("测试","dddd");
        }).start();
    }
    public void test1(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.i("测试","dddd1");
            }
        }).start();
    }
}

After compiling to an APK, the bytecode for test1 (anonymous inner class) shows the creation of TestInner$1 and an implicit reference to the outer TestInner instance.

.method public test1()V
    .registers 3
    .line 14
    new-instance v0, Ljava/lang/Thread;
    new-instance v1, Lcom/example/jnihelper/TestInner$1;
    invoke-direct {v1, p0}, Lcom/example/jnihelper/TestInner$1;->
(Lcom/example/jnihelper/TestInner;)V
    invoke-direct {v0, v1}, Ljava/lang/Thread;->
(Ljava/lang/Runnable;)V
    .line 19
    invoke-virtual {v0}, Ljava/lang/Thread;->start()V
    .line 20
    return-void
.end method

The constructor of TestInner$1 stores the outer instance in a field this$0 , confirming the reference.

For the lambda version, the compiled bytecode shows the creation of a synthetic class TestInner$$ExternalSyntheticLambda0 that does not hold the outer reference when the lambda does not access any outer members:

.method static synthetic Lambda表达式()V
    .registers 2
    .line 9
    const-string v0, "\u6d4b\u8bd5"
    const-string v1, "dddd"
    invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
    .line 10
    return-void
.end method
.method public test()V
    .registers 3
    .line 8
    new-instance v0, Ljava/lang/Thread;
    sget-object v1, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->INSTANCE:Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
    invoke-direct {v0, v1}, Ljava/lang/Thread;->
(Ljava/lang/Runnable;)V
    .line 10
    invoke-virtual {v0}, Ljava/lang/Thread;->start()V
    .line 11
    return-void
.end method

The synthetic lambda class implements Runnable but does not store an outer instance, indicating no memory‑leak risk.

Explicit Outer Reference in Lambda

When the lambda explicitly accesses a field of the outer class, the compiler generates a synthetic lambda class that captures the outer instance:

public class TestInner {
    private String helloInner = "helloIIIII";
    public void test(){
        new Thread(() -> {
            Log.i("测试", "dddd");
            Log.i("测试", TestInner.this.helloInner); // explicit reference
        }).start();
    }
}

The resulting bytecode shows the synthetic lambda constructor receiving the outer TestInner as a parameter and storing it in a field f$0 :

.method public synthetic constructor
(Lcom/example/jnihelper/TestInner;)V
    .registers 2
    invoke-direct {p0}, Ljava/lang/Object;->
()V
    iput-object p1, p0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->f$0:Lcom/example/jnihelper/TestInner;
    return-void
.end method
.method public final run()V
    .registers 2
    iget-object v0, p0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->f$0:Lcom/example/jnihelper/TestInner;
    invoke-virtual {v0}, Lcom/example/jnihelper/TestInner;->lambda$test$0$com-example-jnihelper-TestInner()V
    return-void
.end method

Thus, a lambda only holds the outer reference when it actually accesses outer members.

Conclusion

Anonymous inner classes always retain a reference to their enclosing class, posing a memory‑leak risk. Lambda expressions do not capture the outer instance unless the lambda body explicitly references it, making them a safer alternative for avoiding leaks in Android development.

JavaandroidBytecodelambdaMemoryLeakAnonymousInnerClass
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.