内存泄漏:内存不再GC的掌控中。也就是一个对象不再需要使用,本该被回收时,但是有另外一个对象持有该对象,导致对象不能回收。这种导致本该回收的对象不能回收且停留在堆内存中,这就是内存泄漏。
内存分配策略:
区别:1:堆是不连续的内存区域,堆空间比较灵活也特别大。
2: 栈式一块连续的内存区域,大小是有操作系统觉决定的。
3:堆管理很麻烦,频繁地new/remove会造成大量的内存碎片,这样就会慢慢导致效率低下。对于栈的话,他先进后出,进出完全不会产生碎片,运行效率高且稳定。
public class Student{
int a = 1;
Dimen d1= new Dimen ();
public void XXX(){
int b = 1;//栈里面
Dimen d2 = new Dimen ();
}
}
1.成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)---因为他们属于类,类对象最终还是要被new出来的。
2.局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。-----因为他们属于方法当中的变量,生命周期会随着方法一起结束。
内存泄露,主要讨论堆内存,他存放的就是引用指向的对象实体。
内存泄露(Memory Leak):
进程中某些对象已经没有使用价值了,但是他们却还可以直接或者间接地被引用到GC Root导致无法回收。
当内存泄露过多的时候,再加上应用本身占用的内存,日积月累最终就会导致内存溢出OOM.
内存溢出(OOM):
当应用占用的heap资源超过了Dalvik虚拟机分配的内存就会内存溢出。比如:加载大图片。
OutOfMenoryError主要有以下几种情况:
1:在ListView或者GridView 等加载大量数据或图片,构造adapter没有使用缓存ContentView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vHolder = null;
//如果convertView对象为空则创建新对象,不为空则复用
if (convertView == null) {
convertView = inflater.inflate(..., null);
// 创建 ViewHodler 对象
vHolder = new ViewHolder();
vHolder.img= (ImageView) convertView.findViewById(...);
vHolder.tv= (TextView) convertView
.findViewById(...);
// 将ViewHodler保存到Tag中
convertView.setTag(vHolder);
} else {
//当convertView不为空时,通过getTag()得到View
vHolder = (ViewHolder) convertView.getTag();
}
// 给对象赋值,修改显示的值
vHolder.img.setImageBitmap(...);
vHolder.tv.setText(...);
return convertView;
}
static class ViewHolder {
TextView tv;
ImageView img;
}
图片非常占用内存,一定要管理好内存,不然很容易内存溢出。
滑出去的图片就回收,节省内存。看ListView的源码----回收对象,还会重用ConvertView。
如果用户反复滑动或者下面还有同样的图片,就会造成多次重复IO(很耗时),
那么需要缓存---平衡好内存大小和IO,算法和一些特殊的java类。
算法:lrucache(最近最少使用先回收)
特殊的java类:利于回收,StrongReference,SoftReference,WeakReference,PhatomReference
StrongReference强引用:
回收时机:从不回收 使用:对象的一般保存 生命周期:JVM停止的时候才会终止
SoftReference,软引用
回收时机:当内存不足的时候;使用:SoftReference
WeakReference,弱引用
回收时机:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止
PhatomReference 虚引用
回收时机:在垃圾回收的时候;使用:合ReferenceQueue来跟踪对象呗垃圾回收期回收的活动; 生命周期:GC后终止
开发时,为了防止内存溢出,处理一些比较占用内存大并且生命周期长的对象的时候,可以尽量使用软引用和弱引用。
软引用比LRU算法更加任性,回收量是比较大的,你无法控制回收哪些对象。
比如使用场景:默认头像、默认图标。
ListView或者GridView等要使用内存缓存+外部缓存(SD卡)
2:资源为关闭引起的内存泄漏
BroadCastReceiver:registerReceiver()
和unregisterReceiver()
要成对出现,通常需要在Activity
的onDestory()
方法去取消注册广播接收者。
Cursor:操作完close。
Bitmap:使用后未调用recycle()。
IO流:用完后及时关闭。
自定义属性attribute:attr.recycle()回收。
当不需要使用的时候,要记得及时释放资源。否则就会内存泄露。
3:不需要用的监听未移除会发生内存泄露
例子1:
// tv.setOnClickListener();//监听执行完回收对象
//add监听,放到集合里面
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean b) {
//监听view的加载,view加载出来的时候,计算他的宽高等。
//计算完后,一定要移除这个监听
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
}
});
例子2:
SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
//不需要用的时候记得移除监听
sensorManager.unregisterListener(listener);
4:集合类泄漏
集合类中如果只有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量(比如类中的静态属性),那么没有 响应的删除机制,很可能导致集合所占用的内存只增不减。
5:无限循环动画
没有在onDestroy中停止动画,否则Activity就会变成泄露对象。
比如:轮播图效果。
6:Context
泄漏 这是一个很隐晦的OutOfMemoryError
的情况。先看一个Android官网提供的例子:
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
这段代码效率很快,但同时又是极其错误的:
我们看一下setBackgroundDrawable(Drawable background)
的源码:
public void setBackgroundDrawable(Drawable background) {
...
background.setCallback(this);
}
有background.setCallback(this);
方法,也就是说Drawable
拥有TextView
的引用,而TextView
又拥有Activity
*(Context类型)*的引用, 因为sBackground
为static
的,即使Activity
被销毁,但是sBackground
的生命周期还没走完,所以内存仍然不会被释放。这样就会有内存泄露了。 对,这样想是对的,但是我们看一下setCallback
的源码:
public final void setCallback(Callback cb) {
mCallback = new WeakReference(cb);
}
我们会发现里面使用了WeakReference
,所以不会存在内存泄露了,但是官网当时提供的例子明明说有泄露,这是因为在3.0之后, 修复了这个内存泄露的问题,在3.0之前setCallback
,方法是没有使用WeakReference
的,所以这种泄露的情况在3.0之前会发生,3.0之后已经被修复。
7: 线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
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();
//耗时的操作
}
}
}
假设MyThread
的run
函数是一个很费时的操作,当调用finish
的时候Activity
会销毁掉吗?
事实上由于我们的线程是Activity
的内部类,所以MyThread
中保存了Activity
的一个引用,当MyThread
的run
函数没有结束时,MyThread
是不会被销毁的, 因此它所引用的Activity
也不会被销毁,因此就出现了内存泄露的问题。
8:单例造成的内存泄漏
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏,比如:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
这里如果传入的是Activity
的Context
,当该Context
的Activity
退出后,由于其被单例对象引用,所以会导致Activity
无法被回收,就造成内存泄漏。
:9:尽量使用ApplicationContext
Context
引用的生命周期超过它本身的生命周期,也会导致Context
泄漏。 所以如果打算保存一个长时间的对象时尽量使用Application
这种Context
类型。 例如:
mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
改成:
mStorageManager = (StorageManager) getApplicationContext().getSystemService(Context.STORAGE_SERVICE);
按道理来说这种系统服务是不会有问题,但是有些厂商在修改的时候,可能会导致Context
无法被及时释放。
10:Handler的使用,在Activity退出的时候注意移除(尤其是循环的时候)
public class ThreadDemo extends Activity {
private static final String TAG = "ThreadDemo";
private int count = 0;
private Handler mHandler = new Handler();
private Runnable mRunnable = new Runnable() {
public void run() {
//为了方便 查看,我们用Log打印出来
Log.e(TAG, Thread.currentThread().getName() + " " +count);
//每2秒执行一次
mHandler.postDelayed(mRunnable, 2000);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//通过Handler启动线程
mHandler.post(mRunnable);
}
}
这样在也会引发内存泄露。我们应该在onDestory
方法中移除Handler
,代码如下:
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mRunnable);
}
11:由上面的Handler
可以引伸出来的匿名内部类、非静态内部类和异步线程导致的内存泄漏。
下面看一个非静态内部类创建静态实例导致的内存泄漏
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}
因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态实例,该实例的声明周期与应用的一样长, 这就导致了静态实例一直会持有Activity
的引用而造成内存泄漏。
下面再看一个匿名内部类和异步现成的现象:
public class MainActivity extends Activity {
...
Runnable ref1 = new MyRunable();
Runnable ref2 = new Runnable() {
@Override
public void run() {
}
};
...
}
上面的例子中ref1
对象是没问题的,但是ref2
这个匿名类的实现对象中有外部类的引用,如果此时线程的生命周期与Activity
的不一致时就会造成了泄漏。
总结一下避免Contex
t泄漏应该注意的问题:
getApplicationContext()
类型。Context
的引用不要超过它本身的生命周期。static
关键字。Activity
里如果有线程或Handler
时,一定要在onDestroy()
里及时停掉。性能优化的帮助工具:
MAT,
Memory Monitor(属于AndroidMonitor中一个模块),
HeapTool(查看堆信息),
Allaction Tracking,
LeakCanary
Lint工具
1:Allaction Tracking
追踪内存分配信息。可以很直观地看到某个操作的内存是如何进行一步一步地分配的。
2:LeakCanary
Square公司
可以直接在手机端查看内存泄露的工具
实现原理:本质上还是用命令控制生成hprof文件分析检查内存泄露。
然后发送通知。
Application
install()
LeakCanary
androidWatcher()
RefWatcher
new AndroidWatcherExecutor() --->dumpHeap()/analyze()(--->runAnalysis())--->Hprof文件分析
new AndroidHeapDumper()
new ServiceHeapDumpListener
3:Lint分析工具
Android Studio很方便 很好用。
检测资源文件是否有没有用到的资源。
检测常见内存泄露
安全问题SDK版本安全问题
是否有费的代码没有用到
代码的规范---甚至驼峰命名法也会检测
自动生成的罗列出来
没用的导包
可能的bug