非静态的内部类会持有外部类的一个隐式引用

本文详细探讨了Android应用中常见的内存泄漏问题,包括Activity和Thread的内存泄漏原因及解决方法,并提供了避免内存泄漏的有效策略。通过实例演示静态内部类与非静态内部类的区别,以及如何在Android应用中正确管理线程生命周期,确保资源的有效释放。

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

转载地址: http://blog.youkuaiyun.com/zhongwcool/article/details/22886067


另外附上一个正在看的内存泄露的技术文,附上链接:https://yq.aliyun.com/articles/3009?spm=5176.100240.searchblog.31.oDWxG1



Activity是如何泄漏的

只要非静态的匿名类对象没有被回收,MainActivity就不会被回收,MainActivity所关联的资源和视图都不会被回收,发生比较严重的内存泄漏。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * Example illustrating how threads persist across configuration 
  3.  * changes (which cause the underlying Activity instance to be 
  4.  * destroyed). The Activity context also leaks because the thread 
  5.  * is instantiated as an anonymous class, which holds an implicit 
  6.  * reference to the outer Activity instance, therefore preventing 
  7.  * it from being garbage collected. 
  8.  */  
  9. public class MainActivity extends Activity {  
  10.    
  11.   @Override  
  12.   protected void onCreate(Bundle savedInstanceState) {  
  13.     super.onCreate(savedInstanceState);  
  14.     exampleOne();  
  15.   }  
  16.    
  17.   private void exampleOne() {  
  18.     newThread() {//匿名内部类,非静态的匿名类会持有外部类的一个隐式引用  
  19.       @Override  
  20.       publicvoid run() {  
  21.         while(true) {  
  22.           SystemClock.sleep(1000);  
  23.         }  
  24.       }  
  25.     }.start();  
  26.   }  
  27. }  
要解决MainActivity的内存泄漏问题,只需把非静态的Thread匿名类定义成静态的内部类就行了(静态的内部类不会持有外部类的一个隐式引用):

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * This example avoids leaking an Activity context by declaring the 
  3.  * thread as a private static inner class, but the threads still 
  4.  * continue to run even across configuration changes. The DVM has a 
  5.  * reference to all running threads and whether or not these threads 
  6.  * are garbaged collected has nothing to do with the Activity lifecycle. 
  7.  * Active threads will continue to run until the kernel destroys your 
  8.  * application's process. 
  9.  */  
  10. public class MainActivity extends Activity {  
  11.    
  12.   @Override  
  13.   protected void onCreate(Bundle savedInstanceState) {  
  14.     super.onCreate(savedInstanceState);  
  15.     exampleTwo();  
  16.   }  
  17.    
  18.   private void exampleTwo() {  
  19.     new MyThread().start();  
  20.   }  
  21.    
  22.   private static class MyThread extends Thread {  
  23.     @Override  
  24.     public void run() {  
  25.       while(true) {  
  26.         SystemClock.sleep(1000);  
  27.       }  
  28.     }  
  29.   }  
  30. }  
现在新创建的Thread不会持有MainActivity的一个隐式引用,当手机屏幕旋转时不会阻止垃圾回收器对旧MainActivity的回收工作。

Thread是如何泄漏的

在提到的第二个问题中,一旦一个新的Activity创建,那么就有一个Thread永远得不到清理回收,发生内存泄漏。Threads在Java中是GC roots;意味着  Dalvik Virtual Machine (DVM) 会为所有活跃的threads在运行时系统中保持一个硬引用,这会导致threads一直处于运行状态,垃圾收集器将永远不可能回收它。出于这个原因,我们应当为threads添加一个结束的逻辑,如下代码所示
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * Same as example two, except for this time we have implemented a 
  3.  * cancellation policy for our thread, ensuring that it is never 
  4.  * leaked! onDestroy() is usually a good place to close your active 
  5.  * threads before exiting the Activity. 
  6.  */  
  7. publicclass MainActivity extends Activity {  
  8.   privateMyThread mThread;  
  9.    
  10.   @Override  
  11.   protectedvoid onCreate(Bundle savedInstanceState) {  
  12.     super.onCreate(savedInstanceState);  
  13.     exampleThree();  
  14.   }  
  15.    
  16.   privatevoid exampleThree() {  
  17.     mThread = new MyThread();  
  18.     mThread.start();  
  19.   }  
  20.    
  21.   /** 
  22.    * Static inner classes don't hold implicit references to their 
  23.    * enclosing class, so the Activity instance won't be leaked across 
  24.    * configuration changes. 
  25.    */  
  26.   privatestatic class MyThread extends Thread {  
  27.     privateboolean mRunning = false;  
  28.    
  29.     @Override  
  30.     publicvoid run() {  
  31.       mRunning = true;  
  32.       while(mRunning) {  
  33.         SystemClock.sleep(1000);  
  34.       }  
  35.     }  
  36.    
  37.     publicvoid close() {  
  38.       mRunning = false;  
  39.     }  
  40.   }  
  41.    
  42.   @Override  
  43.   protectedvoid onDestroy() {  
  44.     super.onDestroy();  
  45.     mThread.close();  
  46.   }  
  47. }  


在上述的代码中,当Activity结束销毁时在onDestroy()方法中结束了新创建的线程,保证了thread不会发生泄漏。但是如果你想在手机配置发生改变时保持原有的线程而不重新创建的话,你可以考虑使用fragment来保留原有的线程,以备下一次使用具体做法可以参考我之前的一篇文章  http://blog.youkuaiyun.com/tu_bingbing/article/details/9274289,关于这方面  APIdemos  中也做了相关的实现。 

结论


在Android中,长时间运行的任务和Acyivity生命周期进行协调会有点困难,如果你不加以小心的话会导致内存泄漏。关于如何处理这个棘手的问题,下面有几个基本的技巧供参考

       1、使用静态内部类/匿名类,不要使用非静态内部类/匿名类.非静态内部类/匿名类会隐式的持有外部类的引用,外部类就有可能发生泄漏。而静态内部类/匿名类不会隐式的持有外部类引用,外部类会以正常的方式回收,如果你想在静态内部类/匿名类中使用外部类的属性或方法时,可以显示的持有一个弱引用。

     2、不要以为Java永远会帮你清理回收正在运行的threads.在上面的代码中,我们很容易误以为当Activity结束销毁时会帮我们把正在运行的thread也结束回收掉,但事情永远不是这样的!Java threads会一直存在,只有当线程运行完成或被杀死掉,线程才会被回收。所以我们应该养成为thread设置退出逻辑条件的习惯。

     3、适当的考虑下是否应该使用线程.Android应用框架设计了许多的类来简化执行后台任务,我们可以使用与Activity生命周期相关联的Loaders来执行简短的后台查询任务。如果一个线程不依赖与Activity,我们还可以使用Service来执行后台任务,然后用BroadcastReceiver来向Activity报告结果。另外需要注意的是本文讨论的thread同样使用于AsyncTasks,AsyncTask同样也是由线程来实现,只不过使用了Java5.0新增并发包中的功能,但同时需要注意的是根据官方文档所说,AsyncTask适用于执行一些简短的后台任务
<think>好的,我现在需要回答用户关于Java中静态内部类是否会被JVM自动回收的问题。首先,我得确认自己对相关概念的理解是否正确。 静态内部类,也就是内部类,是定义在另一个类中的类,并且没有使用static关键字修饰。这样的内部类实例会持有外部类引用。这一点我记得没错,因为静态内部类可以访问外部类的成员变量和方法,所以必须有一个指向外部类实例的引用。 接下来,用户关心的是JVM的自动回收机制,也就是垃圾回收。垃圾回收的条件是对象不再被任何强引用可达。那这里的关键在于,静态内部类实例和外部类实例之间的引用关系是否会影响它们的回收。 假设有一个外部类Outer和一个静态内部类Inner。当创建一个Inner实例时,它必须通过一个Outer的实例来创建,比如Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();。这时候,inner会持有outer的引用。如果outer实例没有被其他对象引用,而只有inner持有它的引用,同时inner还被其他对象引用的话,那么outer就无法被回收,即使它看起来已经不再使用。这种情况下,外部类实例会因为内部类实例的引用而无法被GC回收,从而导致内存泄漏的可能。 反过来,如果外部类实例被其他对象引用,而内部类实例不再被引用,那么内部类实例会被正常回收。此时,外部类实例的引用计数可能还有其他,所以不影响回收内部类实例。 所以,问题的关键在于引用链。只有当内部类实例和外部类实例之间形成循环引用,而没有任何强引用可达时,才会被一起回收。否则,如果有任何一个实例被其他强引用持有,就会阻止另一方的回收。 举个例子,比如在Android开发中,Handler作为Activity的静态内部类,如果Activity被销毁,但Handler仍然被消息队列持有,就会导致Activity无法被回收,这就是常见的内存泄漏情况。这时候需要将Handler改为静态内部类,并弱引用Activity。 因此,结论应该是:静态内部类实例不会自动阻止JVM的回收,但需要确保外部类实例和内部类实例之间没有不必要的强引用残留。如果外部类实例不再被需要,但内部类实例仍然被引用,那么外部类实例就无法被回收,反之亦然。正确管理它们的生命周期和引用关系是关键,避免内存泄漏可能需要使用弱引用静态内部类。 现在需要验证这些知识点是否正确。查阅Java官方文档或权威资料,确认内部类持有外部类引用的机制,以及垃圾回收的条件。确认无误后,整理回答结构,分点说明引用关系、回收条件,并举例说明常见的内存泄漏情况和解决方法。</think>在Java中,静态内部类的实例是否会**被JVM自动回收**,取决于其与外部类实例之间的**引用关系**。以下是逐步分析: --- ### 1. **静态内部类的特性** - 静态内部类(即普通内部类)**持有外部类实例的引用**。 - 例如:`Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();` - 此时,`inner`对象会通过`this$0`字段引用`outer`对象。 --- ### 2. **回收条件** JVM通过**可达性分析算法**判断对象是否可回收。 - **内部类实例回收**: 若内部类实例不再被任何强引用(如变量、集合等)引用,即使外部类实例存在,内部类实例也会被回收。 - **外部类实例回收**: 若外部类实例不再被强引用,但内部类实例仍被引用,则外部类实例**无法被回收**(因内部类持有引用)。 --- ### 3. **内存泄漏风险** ```java public class Outer { class Inner {} // 静态内部类 } // 场景:外部类实例已不再使用,但内部类实例仍被引用 Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); // outer = null; // 未显解除outer的引用 ``` - **问题**:即使`outer`被置为`null`,若`inner`仍被其他对象引用,则`outer`实例因被`inner`引用而无法被回收。 - **解决方案**: 1. 使用`static`修饰内部类静态内部类持有外部类引用)。 2. 手动解除引用(如`inner = null`)。 3. 使用弱引用(`WeakReference`)持有外部类实例。 --- ### 4. **典型应用场景** - **Android中的Handler**: 若将`Handler`定义为静态内部类,且其发送的延迟消息未被处理,会导致`Activity`实例泄漏。 ```java // 错误示例:静态内部类Handler导致内存泄漏 class MyActivity extends Activity { Handler handler = new Handler() { /* ... */ }; } // 正确做法:静态内部类 + 弱引用 static class SafeHandler extends Handler { WeakReference<MyActivity> weakRef; SafeHandler(MyActivity activity) { weakRef = new WeakReference<>(activity); } } ``` --- ### 5. **总结** - 静态内部类的实例**可以**被JVM自动回收,但需确保: 1. 内部类实例未被其他对象强引用。 2. 外部类实例未被内部类实例引用“孤立”。 - **关键点**:通过合理设计(如静态内部类、弱引用)避免循环强引用,确保对象及时释放。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值