前言
对于 c++ 来说,内存泄漏就是new出来的对象没有 delete,俗称野指针;而对于 java 来说,就是 new 出来的 object 放在 Heap 上无法被GC回收
对象的引用方式
- 强引用(StrongReference):new 的对象没释放,JVM 哪怕发生 OOM 错误也不会回收该对象
- 软引用(SoftReference):GC时,当内存不够使用时才回收
- 弱引用(WeakReference):GC时,不管内存是否够用都会回收
- 虚引用(PhantomReference):和没有任何引用一样,随时都可能被回收
Java 内存
Java是在JVM所虚拟出的内存环境中运行的,JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)
- 栈:栈中只存放基本类型和对象的引用(不是对象),LIFO
- 堆:堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
- 方法区:又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量
垃圾回收机制
垃圾回收机制垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象。在JAVA中对象是通过引用使用的。如果再没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达。垃圾回收用于释放不可到达的对象所占据的内存。
实现思想:我们将栈定义为root,遍历栈中所有的对象的引用,再遍历一遍堆中的对象。因为栈中的对象的引用执行完毕就删除,所以我们就可以通过栈中的对象的引用,查找到堆中没有被指向的对象,这些对象即为不可到达对象,对其进行垃圾回收。
内存泄露 VS 内存溢出
内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用
内存泄露 (memory leak):是指程序在申请内存后,无法释放已申请的内存空间
内存泄露原因
在JAVA中JVM的栈记录了方法的调用,每个线程拥有一个栈。在线程的运行过程当中,执行到一个新的方法调用,就在栈中增加一个内存单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量和返回地址。然而JAVA中的局部变量只能是基本类型变量(int),或者对象的引用。所以在栈中只存放基本类型变量和对象的引用。引用的对象保存在堆中
当某方法运行结束时,该方法对应的frame将会从栈中删除,frame中所有局部变量和参数所占有的空间也随之释放。线程回到原方法继续执行,当所有的栈都清空的时候,程序也就随之运行结束
而对于堆内存,堆存放着普通变量。在JAVA中堆内存不会随着方法的结束而清空,所以在方法中定义了局部变量,在方法结束后变量依然存活在堆中
常见的内存泄露
-
Bitmap使用
图片Size。美工给的切图直接使用,很容易造成内存泄漏。所以我们就必须从源头上减少内存的开销。特别是要展示一些缩略图或者对图片质量要求不怎么高的时候,就更应该进行设置。我们可以使用BItmapFactory.Options设置inSampleSize。该参数可以设置显示图的宽高分别为原始图片大小的几分之一BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options(); bitmapFactoryOptions.inJustDecodeBounds = true; bitmapFactoryOptions.inSampleSize = 3; options.inJustDecodeBounds = false; Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap,bitmapFactoryOptions);
图片像素。除了图片的大小外,图片的像素也对内存的消耗有影响,所以如果需求对图片的质量要求不是特别高就可以改下默认值,从而减少开销
- ALPHA_8:每个像素占用1byte内存
- ARGB_4444:每个像素占用2byte内存
- ARGB_8888:每个像素占用4byte内存(默认)
- RGB_565:每个像素占用2byte内存
图片回收。在图片使用完之后,我们需要对图片进行回收,而不是等系统进行回收
if (null != bitmap && !bitmap.isRecycled()) { bitmap.recyle(); bitmap = null; System.gc(); // 只是建议进行垃圾回收,实际会不会回收无法预知 }
-
关闭对象
- Cursor回收:Activity的onDestory()中对cursor进行关闭
- 注销BroadcaseReceiver:广播一定要unregisterReceiver
- 关闭I / O:用完close()
-
Activity
- Context:尽量使用ApplicationContext而不是Activity,因为引用的实例的生命周期很有可能超出Activity的生命周期,从而造成Activity无法回收,导致内存泄漏
- Listener:注册Listener时若传入Activity的引用,onDestroy时记得removeListener
- Handler:Handler和Activity的生命周期是不一样的,所以在Activity销毁时mHandler.removeCallbacks(mRunnable);
- 总结:凡是使用Activity对象(包括this指针)的,当Activity 销毁时记得解绑、销毁。
-
其它
-
Activity中尽量不要定义静态常量
-
切记不要在循环中频繁 new 对象
工具
- eclipse 中引入插件:MAT
- Android Studio:Android Monitor
- 项目集成 : LeakCanary
网友评论