1,分析内存的常用工具
top/procrank
meinfo
Procstats
DDMS
MAT
Finder-Activity
LeakCanary
LeakInspector
工具很多,掌握原理方法,工具随便找两个能用就行
2,java虚拟机
线程私有
程序计数器pc:
相当于一个执行diam的指示器,用来确认下一行执行的代码的地址,每个线程都有一个,没有OOM的区域
虚拟机栈:
我们平时说的栈就是这块区域。java虚拟机规范中定义了outofmemory和stackoverflow异常
本地方法栈:(Native方法)
java虚拟机规范中定义了outofmemory和stackoverflow异常
共享数据区
方法区
ClassLoader加载类信息,常量,静态变量,编译后的代码
OOM
运行时常量池:
字面量 public satic final java常量,
符号引用 类,接口全名,方法名
java堆
虚拟机能管理的最大的一块内存 GC的主战场
OOM
对象实例,数组的内容
3,GC垃圾回收器
gc如何确定内存回收?
引用计数算法:
Object o1=new Object(); 计数+1=1
Object o2;
o2=o1; 计数+1=2
o1=null; 计数为1 o1和o2都不会回收
缺点:互相引用容易出现计算器永不为0
可达性分析算法:
可作为GC ROOT的对象 :虚拟机栈正在运行使用的引用
静态属性 常量
JNI引用的对象
GC是需要2次扫描才回收对象,所以我们可以用finalize去救活丢失引用的对象
例如:
static App a;
@Override
protected void() throws Throwable{
super.finalize();
a=this;
}
回收也和引用类型有关系:
强引用 : Object obj=new Object();
软引用SoftReference : 内存不足时回收,存放一些重要性不是很强又不能随便让清除的对象,比如图片切换到后台不需要马上显示了
弱引用WeakReference:第一次扫到了,就标记下来,第二次扫到直接回收
虚引用PhantomReference:幽灵 幻影引用 不对生存造成任何影响,用于跟踪GC的回收通知
4.内存泄漏
产生的原因:
一个长生命周期的对象持有一个短生命周期对象的引用
通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM
Android Profiler的使用 :
AndroidStudio Run菜单下的profile...
在图型用户界面上选择要分析的一段内存,右键export出来
Allocations: 动态分配对象个数
Deallocation:解除分配的对象个数
Total count :对象的总数
Shallow Size:对象本身占用的内存大小
Retained Size:GC回收能收走的内存大小
Mat工具的使用 :
转换profile文件格式 sdk/platform-tools/hprof-conv.exe
转换命令 hprof-conv -z src dst
打开软件 File菜单下Open Heap Dump... 打开转换好的文件
引发原因:
第三方SDK引发的问题
Android源码引发的问题
常见的错误代码引起的问题
5,内存抖动
内存频繁的分配与回收,(分配速度大于回收速度时,最终会产生OOM)
回收算法:
标记清除算法
复制算法
标记压缩算法
分代回收算法
对象将根据存活的时间被分为:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)
年轻代(Young Generation)
绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快;当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的);此后,每次Eden区满了,就执行一次Minor GC,并将剩余的对象都添加到Survivor0;当Survivor0也满的时候,将其中仍然活着的对象直接复制到Survivor1,以后Eden区执行Minor GC后,就将剩余的对象添加Survivor1(此时,Survivor0是空白的)。当两个存活区切换了几次之后,仍然存活的对象,将被复制到老年代。
从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,一个Survivor中保存着当前还活 着的对象,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。因此,这种方 式分配内存和清理内存的效率都极高,这种垃圾回收的方式就是复制算法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中)。
年老代(Old Generation)
对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次 Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时, 将执行Major GC,也叫 Full GC。
如果对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。
可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。解决的方法是,年老代中维护一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。
永久代(Permanent Generation方法区)
永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。
垃圾收集器
CMS收集器(标记-清除)
CMS采用"标记-清除"算法实现以获取最短回收停顿时间为目标的收集器
初始标记:标记一下GC Roots能直接关联到的对象
并发标记:进行GC Roots Tracing 的过程
重新标记:是为了修正并发标记期间因用户程序继续运行而导致标记产品变动的那一部分对象的标记记录
并发清除:清除不能到达GC Roots的对象
重置线程:更新之前使用过的数据
优点:并发收集,低停顿
缺点:不能处理浮动垃圾,消耗cpu资源多,产生大量碎片
6,优化内存的良好编码习惯
1.数据类型
不要使用比需求更占空间的基本数据类型
2.循环尽量用foreach,少用iterator, 自动装箱尽量少用
3.数据结构与算法的解度处理
数组,链表,栈,树,图。。。。
数据量千级以内可以使用
Sparse数组(key为整数),ArrayMap(key为对象)
性能不如HashMap但节约内存
4.枚举优化
每一个枚举值都是一个单例对象,在使用它时会增加额外的内存消耗,所以枚举相比与 Integer 和 String 会占用更多的内存
枚举可以进行改进
public enum SHAPE{
RECTANGLE,
TRIANGLE,
SQUARE,
CIRCLE
}
public class SHAPE{
public static final int RECTANGLE=0;
public static final int TRIANGLE=1;
public static final int SQUARE=2;
public static final int CIRCLE=3;
}
5.static staticfinal的问题
static会由编译器调用clinit方法进行初始化
static final不需要进行初始化工作,打包在dex文件中可以直接调用,并不会在类初始化申请内存
所以基本数据类型的成员,可以全写成static final
6.字符串的连接尽量少用加号(+),尽量使用stringBuilder
7.重复申请内存的问题
同一个方法多次调用,如递归函数 ,回调函数中new对象,读流直接在循环中new对象等
不要在onMeause() onLayout() onDraw() 中去刷新UI(requestLayout)
8.避免GC回收将来要重用的对象
内存设计模式对象沲+LRU算法
9,Activity组件泄漏
非业务需要不要把activity的上下文做参数传递,可以传递application的上下文
和Activity有关联的对象写成static 如private static Button btn; private static Drawable drawable;
非静态内部类和匿名内部内会持有activity引用,建议大家单独写个文件
单例模式持有activity引用(使用weakRefrence)
handler.postDelayed()问题
如果开启的线程需要传入参数,用弱引接收可解决问题,handler记得清除removeCallbacksAndMessages(null)
10.尽量使用IntentService,而不是Service