一、java内存模型
1.程序计数器pc-----线程私有
占用内存很小;java的多线程是:抢占式的
java中程序计数器pc为:虚拟机字节码地址;
native中程序计数器为:undefined 也就是null;
java虚拟机规范中没有定义此区域有oom
2.虚拟机栈-----线程私有
java虚拟机规范中定义了两种异常
stackOverflow
oom内存溢出
包含:局部变量表,操作栈,方法返回地址,动态链接,额外附加信息;
3.本地方法栈-----线程私有(Native Method Stack)
和虚拟机栈差不多,一个是Java虚拟机的,一个是native层的
java虚拟机规范中定义了两种异常
stackOverflow
oom内存溢出
备注:在有些jvm的实现中,将本地方法栈和虚拟机栈合二为一了(代表:sun公司的hotpost虚拟机);
4.java堆-----数据共享区
虚拟机管理的最大一块内存。GC的主战场(垃圾堆),有oom异常;
5.方法区-----数据共享区
包含:常量,静态变量,类信息,即使编译后的java代码,特殊的class类,运行时常量池(字面量:java的常量,包括一些public static final,符号引用:类+接口 字段名 方法名 描述符)
二、引用类型
强软弱虚
强:Object object= new Object();
软:有用,但不是必须的对象,在内存不足的时候,会将软引用回收;
弱:非必须对象,gc扫过就会回收;
虚:幽灵引用,不会对生存造成任何影响;没有办法得到引用的对象,但是在对象在回收的时候,能够得到通知;
备注:
1.避免OOM----->使用软引用
2.内存得到及时的释放或回收;节省内存----->使用弱引用
Object obj = new Object();
//引用队列,当保存的对象被gc回收时,可以得到回收的引用对象
ReferenceQueue<Object> objectReferenceQueue = new ReferenceQueue<>();
SoftReference<Object> objectSoftReference = new SoftReference<>(obj,objectReferenceQueue);
//得到保存的对象
Object o = objectSoftReference.get();
System.out.println("soft obj:"+o);
obj = null ;
//执行gc不能立即执行回收,只是通知gc进行扫描;
System.gc();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当内存不足时,可以使用objectReferenceQueue.poll()得到引用的对象
//内存不足时使用,可以得到;如果人为的将obj赋值为null,则得不到
Reference<?> poll = objectReferenceQueue.poll();
System.out.println("soft queue:"+poll);
//soft obj:java.lang.Object@2df08e
//soft queue:null;不为null代表被回收了
ReferenceQueue不为null,代表被回收了;
三、内存泄露
内存泄露的根本原因:长生命周期的对象拥有短生命周期对象的引用,短生命周期对象无法被回收;也就是该被回收的对象因为引用问题无法被回收;
1.Alloc Count :申请内存的次数,也就是申请的对象数;
2.Shallow Size:对象占用的内存大小;
3.Retained Size:对象引用组占用的大小;
4.转换mat标准文件
转换工具目录在/SDK/platform-tools/下面
hprof-conv -z src dst :将src转换为dst
例如:hprof-conv -z 1.hprof 1_mat.hprof
5.系统输入法内存泄露bug:
InputMethodManager ----内部类持有->mCurRootView---->也就是系统的DecorView---->这个DecorView持有mContext上下文对象;
解决方法:
@Override
protected void onDestroy() {
super.onDestroy();
//去除系统级别的输入法造成的内存泄露
/**
* InputMethodManager ----内部类持有->mCurRootView---->也就是系统的DecorView---->这个DecorView持有mContext上下文对象;
*
* 这里可以把InputMethodManager看作是GCRoot,只有打断这条链,才能释放mCurRootView持有的上下文对象;
* 因为无法直接获取InputMethodMananger中的mCurRootView对象,只有使用反射来获取
*/
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
Class<? extends InputMethodManager> aClass = inputMethodManager.getClass();
try {
//getDeclaredField() 获取全部的属性(不包含继承所得属性) getField()只能获取public修饰的属性(包含继承所得)
Field mCurRootViewField = aClass.getDeclaredField("mCurRootView");
mCurRootViewField.setAccessible(true);
Object mCurRootView = mCurRootViewField.get(inputMethodManager);
if (null != mCurRootView) {
Context context = ((View) mCurRootView).getContext();
if (context == this) {
//破坏gc引用链
mCurRootViewField.set(inputMethodManager,null);
}
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
5.检查内存泄露 除了使用mat 还有使用LeakCanary工具;
6.如何防止单例模式内存泄露?
如果必须传入Activity的上下文对象,可以使用弱引用来持有,便于及时被gc回收;
如果非必须 可以使用application的上下文对象;
7.IO操作如何防止内存泄露?
在IO操作中,必须要在finally中进行资源的关闭和释放;
8.Handler或Thread引起的内存泄露?
主要由于使用内部类的原因(非静态内部类持有外部类的引用);
解决方式:使用静态内部类+弱引用方式;
image.png
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,
image.png
使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
Thread的内存泄露和Handler一样,处理方式也是一样的;Thread内存泄露的主要原因是因为线程生命周期的不可控。比如线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。
9.集合类产生的内存泄露?
集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。
10.静态成员引起的内存泄露?
Static成员作为gc root,如果一个对象被static声明,这个对象会一直存活直到程序进程停止。
网友评论