Android 内存泄漏整理
来源:互联网 发布:笔记本跑分软件 编辑:程序博客网 时间:2024/05/21 06:45
Android 内存泄漏整理
参考文献:
1、https://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/#icomments
2、http://blog.csdn.net/anxpp/article/details/51325838
3、http://www.jianshu.com/p/ac00e370f83d
4、http://www.jianshu.com/p/c5ac51d804fa
理论
JAVA 中的内存管理
要了解Java中的内存泄漏,首先就得知道Java中的内存是如何管理的。
在Java程序中,我们通常使用new为对象分配内存,而这些内存空间都在堆(Heap)上。
下面看一个示例:
public static void main(String args[]){
Object object1 = new Object();//obj1
Object object2 = new Object();//obj2
object2 = object1;
//...此时,obj2是可以被清理的
}
Java使用有向图的方式进行内存管理:
在有向图中,我们叫作obj1是可达的,obj2就是不可达的,显然不可达的可以被清理。
内存的释放,也即清理那些不可达的对象,是由GC决定和执行的,所以GC会监控每一个对象的状态,包括申请、引用、被引用和赋值等。释放对象的根本原则就是对象不会再被使用:
•给对象赋予了空值null,之后再没有调用过。
•另一个是给对象赋予了新值,这样重新分配了内存空间。
通常,会认为在堆上分配对象的代价比较大,但是GC却优化了这一操作:C++中,在堆上分配一块内存,会查找一块适用的内存加以分配,如果对象销毁,这块内存就可以重用;而Java中,就想一条长的带子,每分配一个新的对象,Java的“堆指针”就向后移动到尚未分配的区域。所以,Java分配内存的效率,可与C++媲美。
但是这种工作方式有一个问题:如果频繁的申请内存,资源将会耗尽。这时GC就介入了进来,它会回收空间,并使堆中的对象排列更紧凑。这样,就始终会有足够大的内存空间可以分配。
gc清理时的引用计数方式:当引用连接至新对象时,引用计数+1;当某个引用离开作用域或被设置为null时,引用计数-1,GC发现这个计数为0时,就回收其占用的内存。这个开销会在引用程序的整个生命周期发生,并且不能处理循环引用的情况。所以这种方式只是用来说明GC的工作方式,而不会被任何一种Java虚拟机应用
内存泄漏
Java中的内存泄漏,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄漏。
java中的源码避免内存泄漏的实例:
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
实例
static Activity
在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。
如果这个静态变量在Activity生命周期结束后没有清空,就导致内存泄漏。因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。
static Activity activity;
void setStaticActivity() {
activity = this;
}
View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticActivity();
nextActivity();
}
});
解决方案
弱引用不会阻止对象的内存释放,所以即使有弱引用的存在,该对象也可以被回收。
private static WeakReference<MainActivity> activityReference;
void setStaticActivity() {
activityReference = new WeakReference<MainActivity>(this);
}
或者在使用完成处,手动进行释放。
Static Views
类似的情况会发生在单例模式中,如果Activity经常被用到,那么在内存中保存一个实例是很实用的。正如之前所述,强制延长Activity的生命周期是相当危险而且不必要的,无论如何都不能这样做。
特殊情况:如果一个View初始化耗费大量资源,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),像这样,当Activity被销毁时,应当释放资源。
static view;
void setStaticView() {
view = findViewById(R.id.sv_button);
}
View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticView();
nextActivity();
}
});
解决方案
同static Activity
Inner Classes
继续,假设Activity中有个内部类,这样做可以提高可读性和封装性。将如我们创建一个内部类,而且持有一个静态变量的引用,恭喜,内存泄漏就离你不远了
内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。
private static Object inner;
void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}
View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
createInnerClass();
nextActivity();
}
});
解决方案
private Object inner; 不使用static对象。
Anonymous Classes
Handler,Threads,TimerTask
相似地,匿名类也维护了外部类的引用。所以内存泄漏很容易发生,当你在Activity中定义了匿名的AsyncTsk
。当异步任务在后台执行耗时任务期间,Activity不幸被销毁了,这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。
void startAsyncTask() {
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}.execute();
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
startAsyncTask();
nextActivity();
}
});
同样道理,定义匿名的Runnable,用匿名类Handler执行。Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。
void createHandler() {
new Handler() {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}.postDelayed(new Runnable() {
@Override public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}
View hButton = findViewById(R.id.h_button);
hButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
createHandler();
nextActivity();
}
});
我们再次通过Thread和TimerTask来展现内存泄漏。
void spawnThread() {
new Thread() {
@Override public void run() {
while(true);
}
}.start();
}
View tButton = findViewById(R.id.t_button);
tButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
spawnThread();
nextActivity();
}
});
只要是匿名类的实例,不管是不是在工作线程,都会持有Activity的引用,导致内存泄漏。
void scheduleTimer() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}
View ttButton = findViewById(R.id.tt_button);
ttButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
scheduleTimer();
nextActivity();
}
});
解决方案
静态内部类不持有外部类的引用。
private static class NimbleTask extends AsyncTask<Void, Void, Void> {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}
void startAsyncTask() {
new NimbleTask().execute();
}
或者在生命周期结束时,中断或释放相关匿名类的使用。
Sensor Manager
最后,通过Context.getSystemService(int name)可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册监听器,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。
void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
registerListener();
nextActivity();
}
});
解决方案
在Activity结束时注销监听器
private SensorManager sensorManager;
private Sensor sensor;
@Override
public void onDestroy() {
super.onDestroy();
if (sensor != null) {
unregisterListener();
}
}
void unregisterListener() {
sensorManager.unregisterListener(this, sensor);
}
Tips:Android Studio 内存泄漏工具
- Android 内存泄漏整理
- android 内存泄漏之Context--整理-引用
- 内存泄漏整理
- iOS 内存泄漏整理
- 【内存泄漏】Android内存泄漏---单例内存泄漏
- 【内存泄漏】Android内存泄漏---Handler
- Android 内存泄漏调试
- Android 内存泄漏调试
- Android 内存泄漏调试
- Android 内存泄漏调试
- Android 内存泄漏调试
- Android内存泄漏简介
- Android 内存泄漏调试
- Android 内存泄漏调试
- Android 避免内存泄漏
- Android 解析内存泄漏
- Android 内存泄漏调试
- Android 解析内存泄漏
- Rotate Image
- Android自定义视图
- 210. Course Schedule II
- WWDC2017回顾
- CentOS 7 使用composer install 报错phpunit/phpunit 4.8.35 requires ext-dom *
- Android 内存泄漏整理
- Android线程—Timer类(四)
- qtcpsocket调用write和read函数写数据乱码问题解决方法
- 比较有意思的比较内表的小函数
- AndroidStudio导入项目在 Building gradle project info 一直卡住
- AngularJS最理想开发工具WebStorm(转)
- 网易云信-网易验证码短信,网易通知类短信的使用
- 设置cookie与获取cookie
- 高通平台之开机logo,企鹅logo替换