android编译非静态内部类,android non-static内部类导致的内存泄露

博客从.class文件分析了非静态内部类和静态内部类的区别,指出非静态内部类会隐式持有外部类引用。还分析了使用new Handler()导致内存泄露的原因,即非静态内部类和匿名类隐式持有外部类引用。最后给出解决方案,如使用静态内部类和弱引用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从.class文件分析非静态内部类和静态内部类的区别

我们看一个例子就明白了.

public class OuterClass {

public class NormallInnerClass {

public void call() {

fun();

}

}

public static class StaticInnerClass {

public void ask() {

// fun(); //compile error

}

}

public void fun() {

}

}

在OuterClass中定义了2个内部类, 一个是普通的非静态内部类, 另一个是静态内部类.

用javap -c命令对.class文件反编译看下, 注意$前要加上\.

反编译OuterClass$NormallInnerClass.class :

wangxin@wangxin:~/src/browser_6.9.7_forcoopad$ javap -c ./out/production/browser/com/qihoo/browser/OuterClass\$NormallInnerClass.class

Compiled from "OuterClass.java"

public class com.qihoo.browser.OuterClass$NormallInnerClass {

final com.qihoo.browser.OuterClass this$0;

public com.qihoo.browser.OuterClass$NormallInnerClass(com.qihoo.browser.OuterClass);

Code:

0: aload_0

1: aload_1

2: putfield #1 // Field this$0:Lcom/qihoo/browser/OuterClass;

5: aload_0

6: invokespecial #2 // Method java/lang/Object."":()V

9: return

public void call();

Code:

0: aload_0

1: getfield #1 // Field this$0:Lcom/qihoo/browser/OuterClass;

4: invokevirtual #3 // Method com/qihoo/browser/OuterClass.fun:()V

7: return

}

反编译OuterClass$StaticInnerClass.class :

wangxin@wangxin:~/src/browser_6.9.7_forcoopad$ javap -c ./out/production/browser/com/qihoo/browser/OuterClass\$StaticInnerClass.class

Compiled from "OuterClass.java"

public class com.qihoo.browser.OuterClass$StaticInnerClass {

public com.qihoo.browser.OuterClass$StaticInnerClass();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

public void ask();

Code:

0: return

}

对比两个反编译的结果, 普通的非static内部类比static内部类多了一个field: final com.qihoo.browser.OuterClass this$0; 在默认的构造方法中, 用外部类的对象对这个filed赋值.

用intellij idea打开OuterClass$NormallInnerClass.class, 可以看到内部类调用外部类的方法就是通过这个filed实现的. 这也就是static 内部类无法调用外部类普通方法的原因,因为static内部类中没有这个field: final com.qihoo.browser.OuterClass this$0;

package com.qihoo.browser;

import com.qihoo.browser.OuterClass;

public class OuterClass$NormallInnerClass {

public OuterClass$NormallInnerClass(OuterClass var1) {

this.this$0 = var1;

}

public void call() {

this.this$0.fun();

}

}

分析使用new Handler()导致的内存泄露

下面是常见的代码片段:

public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

// ...

}

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

// 延时10分钟发送一个消息

mLeakyHandler.postDelayed(new Runnable() {

@Override

public void run() { }

}, 60 * 10 * 1000);

// 返回前一个Activity

finish();

}

}

上面这段代码会导致内存泄露,它如何发生的?让我们确定问题的根源,先写下我们所知道的.

1、当一个Android应用程序第一次启动时,Android框架为应用程序的主线程创建一个Looper对象。一个Looper实现了一个简单的消息队列,在一个循环中处理Message对象。所有主要的应用程序框架事件(如Activity生命周期方法的调用,单击按钮,等等)都包含在Message对象中,它被添加到Looper的消息队列然后一个个被处理。主线程的Looper在应用程序的整个生命周期中都存在。

2、当一个Handler在主线程中被实例化,它就被关联到Looper的消息队列。每个被发送到消息队列的消息会持有一个Handler的引用,以便Android框架可以在Looper最终处理这个消息的时候,调用这个Message对应的Handler的handleMessage(Message)。

public final class Message implements Parcelable {

...

Handler target;

...

}

3、在Java中,非静态的内部类和匿名类会隐式地持有一个他们外部类的引用, 也就是之前提到的final com.qihoo.browser.OuterClass this$0;。静态内部类则不会。

4、通过handler发送的runnable对象,会被进一步包装为message对象,放入消息队列.

所以, 对上面的例子来说, 当这个Activity被finished后,延时发送的消息会继续在主线程的消息队列中存活10分钟,直到他们被处理。这个message持有handler对象,这个handler对象又隐式持有着SampleActivity对象.直到消息被处理前,这个handler对象都不会被释放, 因此SampleActivity也不会被释放。注意,这个匿名Runnable类对象也一样。匿名类的非静态实例持有一个隐式的外部类引用,因此SampleActivity将被泄露。

为了解决这个问题,Handler的子类应该定义在一个新文件中或使用静态内部类。静态内部类不会隐式持有外部类的引用。所以不会导致它的Activity泄露。如果你需要在Handler内部调用外部Activity的方法,那么让Handler持有一个Activity的弱引用(WeakReference)是正确的解决方案。为了解决我们实例化匿名Runnable类可能导致的内存泄露,我们将用一个静态变量来引用他(因为匿名类的静态实例不会隐式持有它的外部类的引用)。

public class SampleActivity extends Activity {

/**

* 匿名类的静态实例不会隐式持有他们外部类的引用

*/

private static final Runnable sRunnable = new Runnable() {

@Override

public void run() {

}

};

private final MyHandler mHandler = new MyHandler(this);

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

// 延时10分钟发送一个消息.

mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

// 返回前一个Activity

finish();

}

/**

* 静态内部类的实例不会隐式持有他们外部类的引用。

*/

private static class MyHandler extends Handler {

private final WeakReference mActivity;

public MyHandler(SampleActivity activity) {

mActivity = new WeakReference(activity);

}

@Override

public void handleMessage(Message msg) {

SampleActivity activity = mActivity.get();

if (activity != null) {

// ...

}

}

}

}

一句话, 都是java语法上隐式持有特性惹的祸,所以我们要对java语法有深入的理解, 不能只浮于表面.

<think>好的,我现在需要回答用户关于Java静态内部类静态内部类的区别的问题。首先,我得回忆一下这方面的知识,确保自己理解正确。然后按照用户的要求,结构清晰地分步骤解释,同时注意使用正确的中文术语和LaTeX格式。 首先,静态内部类静态内部类的主要区别可能在于它们与外部类的关系。我记得静态内部类不依赖于外部类的实例,可以直接创建,而静态内部类需要先有外部类的实例才能创建。这点需要确认,比如如何实例化这两种内部类。 然后,访问权限方面。静态内部类可能可以访问外部类的所有成员变量和方法,包括私有成员,而静态内部类可能只能访问外部类的静态成员。这是因为静态内部类持有外部类的引用,而静态内部类没有。这需要验证是否正确。 还有内存方面的考虑。静态内部类可能会引起内存泄漏,因为它们持有外部类的引用,而静态内部类不会,这可能是在使用时的注意事项。例如,在Android开发中,Handler如果作为静态内部类可能会导致内存泄漏,所以有时会用静态内部类加弱引用来解决。 另外,关于语法上的区别,静态内部类在定义时使用static关键字,而静态则不需要。实例化的时候,静态内部类可以直接通过外部类名访问,比如Outer.StaticInner obj = new Outer.StaticInner();而静态内部类需要先有外部类的实例,如Outer outer = new Outer(); Outer.NonStaticInner inner = outer.new NonStaticInner(); 这些例子是否正确需要确认。 可能还有其他区别,比如序列化的问题,或者设计模式中的应用,比如静态内部类常用于单例模式,比如静态内部类实现延迟加载的单例。这也是一个点。 接下来需要组织这些信息,按照结构清晰的方式分点说明。可能需要分成几个部分,比如定义、实例化方式、访问权限、内存影响、使用场景等。同时确保每个部分都有明确的对比,让用户容易理解。 还要注意用户提到的LaTeX格式要求。虽然这个问题主要是Java语法,但如果有数学表达式的话需要用$...$,不过这里可能用不上。不过用户要求所有行内数学表达式必须使用该格式,所以如果有变量或代码中的符号可能需要处理,但Java类名一般不需要,可能这里不需要使用LaTeX,但需要确认。 另外,用户要求回答结构清晰,逐步解决问题,所以可能需要先给出基本概念,再分点详细说明区别,最后总结应用场景或使用建议。 现在需要检查是否有遗漏的点,或者是否存在错误。例如,是否静态内部类不能有静态成员?可能静态内部类不能声明静态成员(除了静态常量),而静态内部类可以。这点可能需要补充。 总结一下,区别点包括: 1. 实例化方式的不同 2. 对外部类成员的访问权限 3. 内存泄漏风险 4. 静态成员的定义能力 5. 设计上的用途,如单例模式 需要将这些点逐一解释清楚,使用示例代码来说明,但用户可能不需要完整的代码,只需要说明语法结构。比如在实例化时,静态内部类不需要外部类实例,而静态需要。 最后,确保语言准确,避免技术错误。比如,静态内部类确实隐含持有外部类的引用,而静态内部类没有,因此它们访问外部类成员的能力不同。</think>Java中的静态内部类静态内部类主要区别体现在以下五个方面,我们逐步分析: ### 一、定义方式 - **静态内部类**:需使用`static`关键字修饰 ```java class Outer { static class StaticInner {} // 静态内部类 } ``` - **静态内部类**:无需`static`关键字 ```java class Outer { class NonStaticInner {} // 静态内部类 } ``` --- ### 二、实例化方式 - **静态内部类**:**无需外部类实例**,直接创建 ```java Outer.StaticInner staticInner = new Outer.StaticInner(); ``` - **静态内部类**:**必须依赖外部类实例**才能创建 ```java Outer outer = new Outer(); Outer.NonStaticInner nonStaticInner = outer.new NonStaticInner(); ``` --- ### 三、访问外部类成员 - **静态内部类**: - 仅能访问外部类的**静态成员** - 无法直接访问外部类的实例变量/方法 ```java class Outer { static int staticVar = 1; int instanceVar = 2; static class StaticInner { void print() { System.out.println(staticVar); // ✅ 允许访问静态变量 // System.out.println(instanceVar); ❌ 编译错误 } } } ``` - **静态内部类**: - 可访问外部类的**所有成员**(包括私有实例变量/方法) - 隐含持有外部类的引用`Outer.this` ```java class Outer { int instanceVar = 2; class NonStaticInner { void print() { System.out.println(instanceVar); // ✅ 直接访问实例变量 System.out.println(Outer.this.instanceVar); // ✅ 等价写法 } } } ``` --- ### 四、内存管理 - **静态内部类**: - **不持有外部类引用**,无内存泄漏风险 - 适合长期存活的对象(如线程、缓存) - **静态内部类**: - **隐式持有外部类引用**,若内部类生命周期长于外部类,会导致外部类无法回收 - 典型场景:Android中`Handler`若为静态内部类,需改用静态内部类+弱引用 --- ### 五、静态成员定义 - **静态内部类**: - 可声明**静态成员**(变量/方法) ```java static class StaticInner { static int count = 0; // ✅ 允许 } ``` - **静态内部类**: - **不可声明静态成员**(除`static final`常量) ```java class NonStaticInner { // static int count = 0; ❌ 编译错误 static final int MAX = 100; // ✅ 允许 } ``` --- ### 总结:如何选择? | **场景** | **推荐类型** | |-----------------------|------------------| | 需要独立于外部类实例 | 静态内部类 | | 需访问外部类实例成员 | 静态内部类 | | 避免内存泄漏(如异步任务) | 静态内部类+弱引用 | | 实现单例模式(延迟加载) | 静态内部类(经典写法) | #### 单例模式示例 ```java class Singleton { private Singleton() {} private static class Holder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; // 延迟加载,线程安全 } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值