谈谈什么情况下会导致内存泄露

Android 的虚拟机是基于寄存器的 Dalvik,它的最大堆大小一般是 16M,有的机器为 24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现 OutOfMemory 的错误。
内存溢出的几点原因:

1、资源释放问题
程序代码的问题,长期保持某些资源,如 Context、Cursor、IO 流的引用,资源得不到释放造成内存泄露。

2、对象内存过大问题
保存了多个耗用内存过大的对象(如 Bitmap、XML 文件),造成内存超出限制。

3、static 关键字的使用问题
static 是 Java 中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用 static 修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context 的情况最多),这时就要谨慎对待了。

public class ClassName {
    private static Context mContext;
    //省略
}

以上的代码是很危险的,如果将 Activity 赋值到 mContext 的话。那么即使该 Activity 已经 onDestroy,但是由于仍有对象保存它的引用,因此该 Activity 依然不会被释放。

我们举 Android 文档中的一个例子。

private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
    super.onCreate(state);
    TextView label = new TextView(this); //getApplicationContext
    label.setText("Leaks are bad");
    if (sBackground == null) {
        sBackground = getDrawable(R.drawable.large_bitmap);
}
    label.setBackgroundDrawable(sBackground);
    setContentView(label);
}

sBackground 是一个静态的变量, 但是我们发现, 我们并没有显式的保存 Contex 的引用, 但是, 当 Drawable与 View 连接之后,Drawable 就将 View 设置为一个回调,由于 View 中是包含 Context 的引用的,所以,实际上我们依然保存了 Context 的引用。这个引用链如下:

Drawable->TextView->Context

所以,最终该 Context 也没有得到释放,发生了内存泄露。

针对 static 的解决方案
① 应该尽量避免 static 成员变量引用资源耗费过多的实例,比如 Context。
② Context 尽量使用 ApplicationContext,因为 Application 的 Context 的生命周期比较长,引用它不会出现内存泄露的问题。
③ 使用 WeakReference 代替强引用。比如可以使用 WeakReference mContextRef;

4、线程导致内存溢出
线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    new MyThread().start();
}
private class MyThread extends Thread{
@Override
    public void run() {
    super.run();
    //do somthing while(true)
        }
    }
}

这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设 MyThread 的 run 函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一 般情况下当屏幕转换时会重新创建 Activity,按照我们的想法,老的 Activity 应该会被销毁才对,然而事实上并非如此。

由于我们的线程是 Activity 的内部类,所以 MyThread 中保存了 Activity 的一个引用,当 MyThread 的 run 函数没有结束时,MyThread 是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。

有些人喜欢用 Android 提供的 AsyncTask,但事实上 AsyncTask 的问题更加严重,Thread 只有在 run 函数不结束时才出现这种内存泄露问题,然而 AsyncTask 内部的实现机制是运用了 ThreadPoolExcutor,该类产生的 Thread 对象的生命周期是不确定的,是应用程序无法控制的,因此如果 AsyncTask 作为 Activity 的内部类,就更容易出现内存泄露的问题。
针对这种线程导致的内存泄露问题的解决方案:
第一、将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有) 。
第二、在线程内部采用弱引用保存 Context 引用。

<think> 我们正在讨论Java内存模型(JMM)的相关信息。根据用户要求,我们需要参考之前提供的引用内容来构建回答。 注意:用户要求中提到了系统级指令,包括数学表达式的格式要求、中文回答、生成相关问题等。但当前问题关于JMM,暂时不涉及数学表达式,因此我们主要关注内容组织。 根据引用内容,我们可以总结JMM的几个关键点: 1. JMM是什么(定义和目的) 2. JMM的组成部分(主内存、工作内存) 3. JMM与硬件内存架构的关系 4. JMM如何解决并发问题(规则和机制) 回答结构建议: 1. JMM的定义和目的 2. JMM的核心结构(主内存和工作内存) 3. JMM与硬件内存的关系 4. JMM的内存交互操作(8种原子操作)和规则(happens-before等) 5. JMM如何保证并发安全(可见性、有序性、原子性) 注意:由于引用内容有限,我们主要基于引用内容,并补充必要知识(确保准确)来回答。 同时,按照要求,在回答末尾生成相关问题。 另外,在回答中引用参考的地方需要标注引用标识,如[^1]。注意:引用标识需要与之前提供的引用编号对应,但用户提供的引用中关于JMM的有多个(引用1-5都是关于JMM的),我们可以根据内容选择合适的引用标注。 由于引用1、2、3、4、5都涉及JMM,我们可以在相关的地方标注。 开始构建回答: </think> ### Java内存模型(JMM)深度解析 #### 1. **JMM的定义与核心目标** Java内存模型(JMM)是Java虚拟机规范定义的**抽象内存模型**,旨在解决两大核心问题: - **平台兼容性**:屏蔽不同硬件(CPU架构)和操作系统(内存管理)的访问差异[^1][^2] - **并发安全性**:规范多线程环境下变量的读写规则,确保一致性内存访问效果[^4] JMM本质上是一组**规则和规范**,而非物理存在。它定义了线程如何与主内存交互,以及线程私有工作内存的抽象概念[^3][^5]。 #### 2. **JMM核心结构** ``` +-----------------+ 读取/写入 +-----------------+ | 主内存 | <----------------> | 工作内存 | | (Main Memory) | 同步操作 | (Working Memory) | | - 共享变量 | | - 局部变量 | | - 静态变量 | | - 主内存副本 | +-----------------+ +-----------------+ ^ ^ ^ | | | | | +---------+---------+---------+ | | | +-------------+ +------------+ | +-----------------+-------------------------+ 线程1 线程2 线程3 ``` - **主内存**:存储所有共享变量(实例字段、静态字段),全局可见 - **工作内存**:线程私有空间,存储主内存变量的**副本**和局部变量[^5] - **交互协议**:通过8种原子操作实现主内存与工作内存的同步(`lock`/`unlock`/`read`/`load`/`use`/`assign`/`store`/`write`)[^4] #### 3. **JMM三大特性** 1. **原子性(Atomicity)** - 保证基本类型(除long/double)的读写操作原子性 - 通过`synchronized`和`Lock`扩展原子操作范围 2. **可见性(Visibility)** - **问题场景**:线程A修改共享变量,线程B无法立即感知 - **解决方案**: ```java volatile int counter = 0; // volatile强制刷新主内存 synchronized(lock) { counter++; } // 同步块自动刷新 ``` 3. **有序性(Ordering)** - **问题场景**:指令重排序导致执行顺序与代码不一致 - **控制手段**: - `volatile`禁止指令重排(内存屏障) - `synchronized`保证临界区串行执行 - `happens-before`规则(程序顺序规则、锁规则等)[^1] #### 4. **JMM与硬件架构关系** ``` +---------------------+ +---------------------+ | JMM抽象层 | | 物理硬件层 | | - 主内存 | | - RAM主内存 | | - 工作内存 | | - CPU寄存器 | +----------+----------+ | - CPU高速缓存(L1/L2)| | 映射关系 +----------+----------+ +---------------------------->| ``` - JMM的"主内存"对应物理内存+CPU缓存 - "工作内存"主要对应CPU寄存器和私有缓存[^3] - **关键差异**:JMM是逻辑划分,硬件架构是物理实现[^4] #### 5. **并发问题根源** 当违反JMM规则时会导致: 1. **脏读**:线程读取过时副本 2. **丢失更新**:多线程覆盖写操作 3. **内存不一致**:代码顺序与执行顺序错位 **解决方案示例**: ```java public class Counter { private volatile int value; // 保证可见性与有序性 public synchronized void increment() { // 保证原子性 value++; } } ``` #### 6. **JMM实际应用场景** 1. 双检锁单例模式(`volatile`防指令重排) 2. `ConcurrentHashMap`分段锁设计 3. `AtomicInteger`的CAS无锁实现 4. `ThreadLocal`线程私有存储 > JMM通过定义**内存屏障**(Memory Barrier)控制读写顺序,现代JVM在`volatile`和锁操作中自动插入屏障指令(如LoadLoad/StoreStore屏障)[^1][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值