一、什么是内存泄露
内存不在GC掌控之内了。就是当一个对象已经不需要再使用了,本该被回收时,而又另外一个正在使用的对象持有它的引用从而导致对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中时,就导致了内存泄露。
二、内存分配的几种策略
1、静态存储区:内存在编译期间就分配好,这块的内存在整个运行期间都一直存在。它主要存放静态数据、全局的static数据和一些常量
2、栈式的
在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束之后就会自动被释放掉。栈内存的运算速度很快,因为内置在处理器里面,但是容量有限。
3、堆式的
也叫动态内存分配。有时候可以通过malloc或者new来申请分配一块内存。在C/C++可能需要自己负责释放(java里面直接依赖GC机制)。在C/C++里面是可以自己掌控内存的,需要有很高的素质来解决内存的问题。java需要编程的时候注意自己良好的编程习惯。
区别:
堆是不连续的内存区域,堆空间比较灵活,也比较大。栈内存是一块连续的内存区域,大小由操作系统决定。堆管理很麻烦,频繁的new/remove会造成大量的内存碎片,这样就会慢慢导致效率低下;对于栈,他先进后出,进出不会产生碎片,运行效率高且稳定。
public class Main{
int a = 1;//引用和对象都存在于堆中
Student s = new Student();//引用和对象都存在于堆中
public void XXX(){
int b = 1;//栈里面
Student s2 = new Student();//引用在栈,对象在堆中
}
}
注意:
1、成员变量全部存在堆中(包括基本数据类型、引用和引用的对象实体)——因为他们都属于类,类对象最终还是要被new出来的
2、局部变量的基本数据类型和引用存储在栈中,引用的对象实体存在于堆中——因为他们都属于方法当中的变量,生命周期会随着方法一起结束。
我们所说的内存泄露,主要说的是堆内存的泄露,他存放的就是引用指向的对象实体。
三、java中的四种引用类型
1、StrongReference强引用:
回收时机:从不回收 使用:对象的一般保存 生命周期:JVM停止的时候才会种植
2、SoftReference软引用:
回收时机:当内存不足的时候;使用:SoftReference<String>结合ReferenceQueue构造,有效期短。生命周期:内存不足时终止
3、WeakReference弱引用:
回收时机:在垃圾回收的时候;使用:同软引用;生命周期:GC后终止
4、PhatomReference虚引用:
回收时机:在垃圾回收的时候;使用:结合ReferenceQueue来跟踪对象被垃圾回收器回收的回收活动;生命周期:GC后终止
开发的时候,为了防止内存泄露,处理一些占用内存大且生命周期长的对象的时候,可以尽量使用软引用和弱引用。
四、常见的内存泄露
1、单例模式内存对象无法释放导致内存泄露
单例模式如果使用不当,很容易造成内存泄露。由于单例额就静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象已经不需要使用了,但是单例对象依然持有该对象的引用,那么这个对象就不会被正常回收,导致泄露。例子:
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context){
this.context = context;
}
public static CommUtil getInstance(Context context){
if(instance == null){
instance = new CommUtil(context);
}
return instance;
}
}
这个例子中,如果在获取单例的时候,传入的context是Activity的context,那么,如果当Activity需要被销毁的时候,由于此Activity的context还被与应用同生命周期的Commutils引用,导致此Activity无法被回收
解决办法:使用ApplicationContext
2、Handler导致的内存泄露
Handler导致的内存泄露也是很常见的,尤其是在一些网络请求回调的时候,如果handler使用不规范就会很容易导致内存泄露。例子:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//处理消息
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
由于mHandler是非静态内部类,持有外部类Activity的引用,Looper轮询器轮询的消息队列MessageQueue里面的Message含有堆handler的引用,间接的也就持有了对Activity的引用,那么当Activity退出的时候,如果消息队列中还有未处理的消息,那么就会导致Activity未及时回收导致内存泄露。
解决办法:将Handler设置成静态的,Handler持有的对象使用弱引用,在OnDestroy中调用Handler.
removeCallbacksAndMessages(null)清除消息队列中的消息
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference; //通过弱引用
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
3、非静态内部类导致内存泄露
非静态内部类包括匿名和非匿名,由于持有对外部类的引用,很容易导致内存泄露,最好的做法就是使用静态内部类,内部类持有的引用使用弱引用
还有一种情况也是由于非静态内部类导致的泄漏问题,比如使用非静态线程类,默认是持有对外部类的引用,如果一个activity需要被销毁,但是线程任务并没有执行完,同样会导致activity无法被及时回收导致内存泄漏
4、资源未关闭导致的内存泄漏
BroadCastReceiver没有解注册,File,Cursor,Bitmap等资源,未在页面销毁的时候及时销毁或关闭,同样会造成内存泄漏