Android性能优化之--内存泄漏
来源:互联网 发布:淘宝人工 编辑:程序博客网 时间:2024/06/06 14:12
性能优化之内存泄漏
在Android开发中,内存泄漏是开发过程中时刻要注意的问题,下面我们就来学习下什么叫内存泄漏,为什么会出现内存泄漏,怎样去解决?
一、Android为应用内存分配机制
首先介绍一下Android的内存机制。在Android的运行机制中,不用的分辨率不同Ram大小的设备会为每个应用分配不同的初始化内存。高分辨率的设备比低分辨率的设备分配更多内存。这个可以在自己的设备根目录system/build.prop文件看到。以下是测试机 乐视LX620的内存分配信息:
dalvik.vm.heapstartsize=8m —-起始分配内存
dalvik.vm.heapgrowthlimit=512m —- 一般情况app申请的最大内存
dalvik.vm.heapsize=512m —- 设置largeheap时,App可用的最大内存
dalvik.vm.heaptargetutilization=0.75 —- GC相关
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m —– GC机制相关
代码:
public void memoryInfo() { Runtime rt = Runtime.getRuntime(); long maxMemory = rt.maxMemory(); Log.d("===maxMemory",Long.toString(maxMemory/(1024*1024))); ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); Log.d("===normalMemory",Long.toString(manager.getMemoryClass())); Log.d("===largeMemory",Long.toString(manager.getLargeMemoryClass()));}
结果:
09-23 16:38:13.831 3930-3930/com.zengke.memory.memorydemo D/===maxMemory: 25609-23 16:38:13.831 3930-3930/com.zengke.memory.memorydemo D/===normalMemory: 25609-23 16:38:13.831 3930-3930/com.zengke.memory.memorydemo D/===largeMemory: 512
把AndroidManifest.xml中的application标签加上:
<application ... android:largeHeap="true" ...> ...</application>
maxMemory的值就可以达到largeMemory。但是不建议这样做,因为如果所有app都这样做的话,那样各个抢着占用手机内存,后果可想而知,所有最根本的方法就是自己有效控制好应用的内存分配。
二、再来说下java的GC垃圾回收机制
一般垃圾回收机制一般做两件基本事情:
- 发现无用的信息对象;
- 回收将无用对象占用的内存空间,使该空间可被程序再次利用;
它的原理就是:把所有的引用关系看做一张图,从这张图的GC root节点,开始寻找对应的引用节点, 找到这个节点后,再去寻找这个节点的引用节点。有点类似于顺藤摸瓜。如果有个节点是不可达的,即这个节点是不被引用的,也就是我们所回收的垃圾。
通过引用链无法访问某个对象的时候,该对象即被回收。
三、再来看看java内存分配的几种策略
静态的:
静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序的整个运行期间都一直存在。它主要存放静态数据,全局的static数据和一些常量;栈式的:
在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上创建,函数执行结束的时候这些存储单元就会自动释放掉。栈内存分配的运算速度很快,因为内置在处理器里面,不过容量有限;堆式的:
也叫动态分配内存,有时候可以用new来申请分配一个内存,在c/c++可能需要自己负责释放(java里面直接依赖GC垃圾回收机制) 。在c/c++里可以自己掌控内存,而java在这方面没有很好的方法解决垃圾内存,需要编程的时候注意自己的良好编程习惯。
区别:
堆是不连续的内存区域,对空间比较灵活也特别大;
栈是一块连续的内存区域,大小有操作系统决定。
堆管理很麻烦,频繁地new/remove会造成大量的内存碎片这样就会慢慢导致效率低下。
对于栈的话,他先进后出,进出完全不会产生碎片。运行效率高切稳定。
public class Main{ int a=1; // a在堆中 Student s1= new Student(); // s1在堆中 public void XXX(){ int b = 1; // 栈里面 Student s2 = new Student(); //s2在堆中 }}
总结:
- 成员变量全部存储在堆中(包括基本数据类型,引用和引用的对象实体)–因为他们属于类,类对象最终还是要被new出来的;
- 局部变量的基本数据类型和引用存储在栈当中,引用的对象实体存储在堆中(就比图s2 指向的new student())—-因为他们属于方法中的变量,生命周期会随着方法一起结束。
我们所讨论的内存泄漏,主要讨论堆内存它存放的就是引用指向的对象实体。
四、下面再来说下Java的引用数据类型
StrongReference 强引用:
回收时机:从不回收
使用:对象的一般保存
生命周期:JVM停止的时候才会停止
SoftReference: 软引用
回收时机:当内存不足的时候
使用:SoftReference 结合Reference构造有效期短;
生命周期: 内存不足时终止;
WeakReference : 弱引用:
回收时机:在垃圾回收的时候;
使用:同软引用;
生命周期:GC时终止
PhatomReference 虚引用:
回收时机:在垃圾回收的时候;
使用:和Reference来跟踪对象被垃圾回收期间回收的活动;
生命周期:GC后终止;
开发时,为了防止内存溢出,处理一些比较占用内存大并且生命周期长的对象的时候,可以尽量使用软引用和弱引用;
五、终于到了—– 内存泄漏:
What?
什么是内存泄漏:内存不在GC掌控之内了;
当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致对象不能被回收。这种到之类本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏。
如果不停发生内存泄漏,应用的占用内存越来越大,最后达到可分配内存最大值,出现内存不足情况,最终导致OOM的发生。
Why?
下面我们来看下内存泄漏的情景:
1. Activity中的handler长期持有activity引用导致activity泄漏:
public class MainActivity extends AppCompatActivity { private final Handler myHandler = new Handler(){ @Override public void handleMessage(Message msg) { //doSomething } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); myHandler.postDelayed(new Runnable() { @Override public void run() { //doSomething } },60*10*1000); }}
原因:由于myHandler延时10分钟就会发送一条消息,当activity finish之后,延时发送的消息会在主线程的消息队列中存货十分钟知道被looper拿出然后给到handler处理。此消息隐式持有外部类handler的引用,而handler有隐式的持有外部类activity的引用,直到消息处理完,这个引用都不会被稀放,因此activity即使finish,但仍然不会被gc回收。
引用的顺序为:MessageQueue –> Message –> Runnable –> Handler – > Activity,从这个
引用链得到Activity与MessageQueue关联,所以Activity不能回gc回收。
解决方式:为了解决Handler隐式的持有外部类引用,我们应当将Handler定义在一个新文件或在Activity中使用静态内部类。因为静态内部类不会持有外部类的引用,这样当activity finish时,handler不会持有activity内存泄漏。如果需要在Handler内部调用外部Activity的方法,正确做法是让Handler持有外部Activity的弱引用(WeakReference),这样当gc扫描的时候,这个弱引用的对象就会被回收。
public class MainActivity extends AppCompatActivity { private final MyHandler mHandler = new MyHandler(this); private static Runnable sRunnable = new Runnable() { @Override public void run() { //doSomething } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler.postDelayed(sRunnable, 60 * 10); this.finish(); } private static class MyHandler extends Handler { private final WeakReference<MainActivity> mActivity; public MyHandler(MainActivity activity) { this.mActivity = new WeakReference<MainActivity>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = mActivity.get(); if (activity != null) { //doSomething } } }}
也可以在onDestory方法中干掉handler中所有的callback和message:
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
2、非静态匿名内部类造成内存泄漏
在Activity中开线程时,直接newThread(Runnable)就可以了。但是如果Activity结束了,而Thread还在跑,就会导致内存泄漏的发生。这是因为new Thread作为静态内部类对象都会隐式持有一个外部类对象的引用,我们所创建的线程就是一Activity中的一个内部类,持有Activity对象的引用,所有当Activity结束了,而子线程还在跑就会导致Activity内存泄漏。如下:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); testThread(); } private void testThread() { new Thread(new Runnable() { @Override public void run() { while (true){ SystemClock.sleep(1000); } } }).start(); }}
new Thread()是匿名内部类,且非静态的。所以会隐式持有外部类的一个引用,只要非静态匿名类对象没有被回收,Acitivity就不会被回收。
解决方法:
同样把Thread定义为静态的内部类,这样就不会持有外部类的引用了。
3、单例的错误引用
CommonUtil :
public class CommonUtil { private Context mContext; private static CommonUtil instance; private CommonUtil(Context context) { mContext = context; } public static CommonUtil getInstance(Context context) { if (instance== null) instance= new CommonUtil(context); return instance; }}
Activity:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CommonUtil instance= CommonUtil.getInstance(this); }
上面代码,在Activity中通过单例实例化CommonUtil时,传入了Activity对象,这个CommonUtil的实例化instance便引用了Activity对象,而instance是单例的,生命周期和整个app一样,所以当该Activity结束时,因为被引用而导致无法进行回收。
4、单例 + 依赖注入:
首先看下一下代码:
LeakActivity:
public class LeakActivity extends AppCompatActivity { private TestManager testManager = TestManager.getInstance(); private MyListener listener=new MyListener() { @Override public void doSomeThing() {} }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); testManager.registerListener(listener); }}
TestManager.java:
public class TestManager { private static final TestManager INSTANCE = new TestManager(); private MyListener listener; public static TestManager getInstance() { return INSTANCE; } public void registerListener(MyListener listener) { this.listener = listener; } public void unregisterListener() { listener = null; }}interface MyListener { void doSomeThing();}
在LeakActivity中TestManager.getInstance()创建对象实例。TestManager中采用单例模式返回一个TestManager实例变量。
TestManager中的实例变量是static静态变量,静态变量和类的生命周期是一样的。类加载的时候,静态变量就被加载,类销毁时,静态变量就被销毁。
因为INSTANCE是一个单例,所以和app的生命周期是一样的。当app进程销毁时,堆内存的INSTANCE对象才会被释放,INSTANCE的生命周期是非常长的。
而又可以看到代码中的Activity里创建了listener非静态内部类,所以listener就持有外部类Activity的引用。随着testManager的registerListener(listener)执行,TestManager的listener就持有了Activity的引用,由此形成了一个引用链。
引用链为:TestManager–> listener –> Activity
所以解决方式:要在Activity的onDestory()方法里注销注册的listener
@Override protected void onDestroy() { testManager.unregisterListener(); super.onDestroy(); }
将TestManager中的listener与Activity的断开。
参考:
http://blog.csdn.net/jkerving/article/details/52246851
- Android性能优化之--内存泄漏
- Android性能优化-内存泄漏
- Android性能优化 --- 内存泄漏
- Android性能优化之常见内存泄漏和优化方案
- [Android分享] Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- Android性能优化之常见的内存泄漏
- static修饰全局变量
- 将kubernetes跑在本地LXD容器中(by quqi99)
- ThinkPHP5.0---------view
- 引用类型数组
- 利用shell分析公网地址来源
- Android性能优化之--内存泄漏
- Struts2转换器
- 我的Chrome插件
- Least Common Multiple
- 关注下micropython1.4.5的pendsv.C以及如何提高编译速度
- 【JavaSE_学习笔记】List接口的三个子实现类
- 第11节:scala面向接口编程
- select下拉列表
- 三层json