java 内存泄漏 基础知识
java 内存的分配策略
静态分配, 栈式分配,堆式分配
对应的
- 静态存储区,也叫方法区,这个内存区里 主要存放一些 静态数据,全局变量等等,这块内存,程序编译的时候已经分配好了,并且在静态存储区中存储的变量,在整个程序运行期间 都存在
- 栈区 ,方法执行的时候,方法体内的局部变量,会在栈上创建内存空间,并在方法执行结束之后,这些变量所持有的内存会被自动释放,因为栈内存分配运算,内置于处理器中,所以效率很高,但是栈区的内存空间 容量有限
- 堆区,又称为动态内存分配,通常就是我们 new 对象出来的内存,这部分内存在不使用的时候,会被 java 的垃圾回收器来负责回收。
栈区,跟堆区 :
栈区:在方法 体内定义的基本类型的变量,和对象的引用变量都是在方法的栈内存中分配的,当一段方法中定义变量时,java就会在栈中为该变量分配内存空间,当超过这个变量的作用域时,这变量就无效了,分配给它的内存空间就被释放,这时候栈区以前占着的内存空间就会被其他方法所使用。
堆区:存放所有 由new 创建的对象,以及数组,在堆中分配的内存,将由java 的垃圾回收器来管理,其中产生的一个数组,或者对象,还可以在栈中定义一个特殊的变量,也就是通过引用变量访问堆中的东西。
java是如何管理内存的
java 内存管理,就是对象的分配和释放问题, 通过 new 为对象申请内存空间,所有的对象都是在堆中分配的,gc 自动释放。简化了程序员的工作,但是加重了java虚拟的工作,这也是java 为啥慢的原因之一。 gc 是有很大作用的,为了正确的释放对象,他必须监控每一个对象的运行状态,包括,对象的申请,引用,被引用,复制等等。
可以把对象考虑为有向图的顶点,将引用关系考虑为有向图的边,从进程顶点开始的一颗有向根树,在有向图中,根顶点可达的对象 都是有效对象,根顶点不可达的对象是可以回收的对象。
java 中的内存泄漏
内存泄漏是指无用对象 持续占有内存,或者 无用对象的内存得不到及时的释放,从而造成的内存空间的浪费称为内存泄漏
Android 中的内存泄漏
单例
不恰当的使用单例,会导致内存泄漏
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;
// }
// 修复内存泄漏的写法:
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();// 使用Application 的context
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
匿名内部类
非静态内部类默认会持有外部类的引用,应该设为 静态内部类
handler
handler 是非静态内部类。
解决:把handler 设为静态内部类,同时 在handler 的内部持有activity 外部类的弱引用。
//容易造成内存泄漏的写法:
// private Handler mHandler = new Handler() {
// @Override
// public void handleMessage(Message msg) {
// //...
// }
// };
//
// @Override
// protected void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
// loadData();
// }
//
// private void loadData() {
// //...request
// Message message = Message.obtain();
// mHandler.sendMessage(message);
// }
//
// static class TestResource {
// private static final String TAG = "";
// //...
// }
// 修复内存泄漏的方法:
★import java.lang.ref.WeakReference;
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
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);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
避免使用 static 成员变量
如果把成员变量声明为static,那么他的生命周期,和整个App的 生命周期是一致的,如果App进程 设计上是常驻内存的,那即使App 切到后台,这部分的static 变量也是不会被释放的,按照我们现在App内存管理机制,占内存较大的后台进程,将优先被回收,所以说当你的进程被回收了之后,你所存的那些变量,那些数据是不安全的。
所以这类问题,在类设计的时候要考虑好,是不是要在初始化的时候去设为静态成员,是不是可以考虑懒加载。如果一定要使用,一定要对这些变量的生命周期做一个管理。
资源未关闭造成的内存泄漏
使用广播啊,contenProvider ,文档啊,游标,socket,Bitmap,在 生命周期销毁的时候,关闭和注销这些资源
AsyncTask 造成的内存泄漏
跟 handler 一样,需要在 声明周期销毁的时候,cancle
Bitmap c 端的内存,需要 recycle()
内存管理机制
从操作系统的角度来说,内存就是一块数据存储区域,而且属于被操作系统调度的资源。
分配内存:操作系统会为每一个进程分配一个合理的内存大小,从而保证每一个进程正常使用。
回收机制:操作系统里的内存回收机制,在内存不足的时候会有一个合理的回收再分配的作用。
Android 内存管理机制
-
分配机制
安卓 在为每一个进程分配内存的时候,采用了一个弹性的分配方式,也就是说:系统一开始,不会为这个APP分配太多的内存,而是为每一个APP进程分配一个小额的量,而这个小额的量是根据每一个移动设备的物理RAM尺寸大小决定的,随着App的不断运行,当前内存不足,android 就会继续为 app 分配更多的内存,当然是有限的。总之,android系统的内存分配机制,就是让更多的进程存活在内存中,当用户下一次启动该APP进程时候,就不需要重新创建进程,而是恢复已有的进程就可以了,提高了体验。 -
回收机制
android对内存的使用,是尽最大限度地使用,这是继承Linux 的特点。
当系统发现内存不足的时候,就会杀死其他进程。为新进程分配内存
所以就有个优先级
前台进程
可见进程
服务进程 :会开启一些服务
前三个不会被杀死
后台进程:存放在一个缓存列表中,数据结构是LRU,先杀死的处于列表的尾部
空进程 :为了平衡系统的性能,Android不会保存这些进程。
还有个回收效益的概念,当Android系统开始杀死进程的时候,系统会判断每一个进程杀死后带来的回收效益,因为android总是倾向于 回收更多的内存,当然杀死的越少越好。
内存管理的目标
- 更少的占用内存
- 在合适的时候,合理的释放系统资源
- 在合理的生命周期中,保存或者还原重要的数据
内存优化的方法
-
service 完成任务后,尽量停止它。因为他是优先级比较低的服务进程,影响内存回收, 用IntentService 替代 service,因为IntentService 继承service内部开启子线程,可以做耗时操作,还可以自动退出。而不是像Service 要手动 stopService。
-
在Ui 不可见的时候,释放掉一些只有Ui使用的资源,有个 OnTreeMemory() 通知App回收资源
-
内存紧张的时候,也是用OnTreeMemory() 通知App回收资源
-
Bitmap 导致的,根据当前设备的分辨率来压缩bitmap,使用bitmap之后 使用recycle() 释放c内存,使用软引用,使用LRU。
-
使用针对内存优化过的数据结构,替代hashmap 的 SparseArray、ArrayMap,同时,尽量少用 枚举,消耗的内存是常量的两倍
-
避免使用依赖注入框架,使用這些框架 除了方便,也会带来一些扫描注解带来的系统资源
-
使用ZIP 对齐的APK
-
使用多进程,定位,push,webView。当然多进程 的安全,数据传输 很难的问题
内存溢出 VS 内存泄漏
内存溢出,其实就是OOM, 还是bitmap ,比如未压缩的bitmap。压缩啊,使用bitmap的属性,裁剪啊,回收啊。
内存泄漏, 就是本该被垃圾回收器 gc 的内存没有被回收。dump 分析 扯一下子。
网友评论