一、内存管理知识
1、内存泄漏:
1) 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2) 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3)一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
4)隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
2.Java对对象的4种引用
1)强引用 :创建一个对象并把这个对象直接赋给一个变量,Person person = new Person(“sunny”); 不管系统资源有么的紧张,强引用的对象都绝对不会被回收,即使他以后不会再用到。
2)软引用 :通过SoftReference类实现,SoftReference<Person> p = new SoftReference<Person>(new Person(“Rain”));,内存非常紧张的时候会被回收,其他时候不会被回收,所以在使用之前要判断是否为null从而判断他是否已经被回收了。
3)弱引用 :通过WeakReference类实现,WeakReference<Person> p = new WeakReference<Person>(new Person(“Rain”));不管内存是否足够,系统垃圾回收时必定会回收。
4)虚引用 :不能单独使用,主要是用于追踪对象被垃圾回收的状态。通过PhantomReference类和引用队列ReferenceQueue类联合使用实现,eg :
3、垃圾回收
1)一般是在CPU空闲或空间不足时自动进行垃圾回收,而程序员无法精确控制垃圾回收的时机和顺序等。
2)当没有任何获得线程能访问一个对象时,该对象就符合垃圾回收条件。
3)垃圾回收不能强制执行,然而Java提供了一些方法:如System.gc()方法允许你请求JVM执行垃圾回收,而不是要求虚拟机会尽其所能满足请求,但是不能保证JVM从内存中删除所有不用的对象。
4、如何显示的使对象符合垃圾回收条件
1)对象设置为null
2)重新为对象赋值,该对象原来的值就符合回收条件
3)方法内创建的对象、隔离引用
二、内存泄漏
Java内存泄露根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。
1)资源性对象未关闭。比如BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap文件等,在不使用或者使用完毕时,应该及时关闭它们;一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存。
2)注册对象未注销。比如Listener监听器事件注册后未注销,会导致观察者列表中维持着对象的引用。
3)类的静态变量持有长周期大数据对象。
4)非静态内部类的静态实例。
public class MainActivity extends AppCompatActivity {
private static Test mTest = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mTest == null){
mTest = new Test();
}
}
class Test {
}
}
1、将内部类改为静态内部类。
2、将这个内部类封装成一个单例,Context使用Application的Context
5)Handler临时性内存泄漏。如果Handler是非静态的,容易导致 Activity 或 Service 不会被回收;创建一个静态内部类继承自handler,然后再在构造参数中对handler持有的对象做弱引用,这样在回收时就会回收了handler持有的对象,这里还做了一处修改,就是当我们的回收了handler持有的对向,即销毁了该Activity时,这时如果handler中的还有未处理的消息,我们就需要在OnDestry方法中移除消息队列中的消息。
public class MainActivity extends AppCompatActivity {
//new一个自定义的Handler
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
//自定义静态内部类继承自Handler
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
//在构造函数中使用弱引用来引用context对象
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() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
//移除队列中所有的Runable和消息
//这里也可以使用mHandler.removeMessage和mHandler.removeCallBacks来移除指定的Message和Runable
mHandler.removeCallbacksAndMessages(null);
}
}
6)线程造成的内存泄漏,线程使用不恰当造成的内存泄漏也是很常见的,当在Activity销毁时也要记得在OnDestry中调用AsyncTask.cancal()方法来取消相应的任务。避免在后台运行浪费资源。
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
}
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(30000);
}
}
}
7)WebView 存在着内存泄漏的问题,在应用中只要使用一次 WebView,内存就不会被释放掉。
//该方法还有问题,在暴力点击切换的webview界面,返回;反复操作上百次,webview白屏切不能恢复
public void destroy() {
if (mWebView != null) {
// 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
try {
mWebView.destroy();
} catch (Throwable ex) {
}
}
}
8)全局性、final或者静态的的集合类(Map、Vector、list等),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减
最简单的方法就是将Vector对象设置为null
当set里面的对象属性被修改后,再调用remove方法时删除不掉
Person p1 = new Person("张三","age1",11);
Person p2 = new Person("李四","age2",12);
Person p3 = new Person("王二麻子","age3",13);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("总共有:"+set.size()+" 个人!"); //结果:总共有:3 个人!
p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变
set.remove(p3); //此时remove不掉,造成内存泄漏
set.add(p3); //重新添加,居然添加成功
System.out.println("总共有:"+set.size()+" 个人!"); //结果:总共有:4 个人!
9)单例设计模式的静态特性会使他的生命周期和应用程序的生命周期一样长,这就说明了如果一个对象不在使用了,而这时单例对象还在持有该对象的引用;如果我们传入的Context是Activity的Context的话,这时如果我们因为需求销毁了该Activity的话,Context也会随着Activity被销毁,但是单例还在持有对该类对象的引用,这时就会造成内存泄漏
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;
}
}
三、参考文章
Java内存泄漏的排查总结:https://blog.csdn.net/fishinhouse/article/details/80781673
LeakCanary:https://blog.csdn.net/qq_39037047/article/details/79563423
内存泄漏的检测流程、捕捉以及分析:https://blog.csdn.net/qq_20280683/article/details/77964208