Java对象生命周期:
- 创建:为对象分配内存空间,构造对象
- 应用:此时 对象至少被一个强引用持有
- 不可见:未被任何强引用持有,进行可达性分析
- 不可达:可达性分析为不可达,进入下一阶段
- 收集:当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了finalize()方法,则会去执行该方法的终端操作。
- 终结:当对象执行完finalize()方法后仍然处于不可达状态时(可达性分析垃圾回收算法被回收前,会有两次标记过程,判断是否执行l finalize()方法,执行完之后判断是否GC ROOT可达,如果仍不可达,则准备回收),则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
- 对象空间进行重新分配
对象的内存布局:
Android内存回收机制:
- 对象创建后在Eden区
- 执行GC后,如果对象仍然存活,则复制到S0区
- 当S0区满时,该区域存活对象将复制到S1区,然后S0区清空,接下来S0和S1角色互换.
- 当第3步达到一定次数(系统版本不同会有差异)后,存活对象将对复制到Old Generation
- 当这个对象在Old Greneration停留的时间达到一定程度时,它会被移动到Old Generation,最后积累一定时间再移动到Permanent Generation区域
年轻代使用mimor GC速度快,老年代使用full GC速度就会慢很多。
Dalvik虚拟机内存分配:
- Linear Alloc:匿名共享内存
- Zygote Space:Zygote相关的一些信息
- Alloc Space:每个进程 独占
ART:
Non Moving Space
Zygote Space
Alloc Space
Image Space:预加载的类信息
Large Obj Space:分配大对象 如bitmap
可达性分析算法GCoot:
Java的四种引用:
-
强引用
-
软引用:GC扫描到不一定被回收,只有内存不足时才会被回收
-
弱引用:被GC扫描到就会被回收
-
虚引用:没有引用
垃圾回收算法:
标记清除法:
特点:
位置不连续,产生碎片,效率略低,两次扫描
多次扫描后会产生大量内存碎片
复制算法:
特点:
实现简单,运行高效,没有内存碎片,利用率只有一半
假设内存又4M,使用的只有2M
标记整理算法:
特点:
没有内存碎片,效率偏低,两遍扫描,指针需要调整
扫描标记可回收后,整理存活对象和可回收对象,分区
分代收集算法:
综合运用
新生区对象存活低使用复制算法,老年区对象存活久,清除频率低,使用标记算法
App内存组成以及限制
Android给每个app分配一个VM,让app运行在dalvik上,这样即使app崩溃也不会影响到系统。系统给VM分配了一定的内存大小,app可以申请使用的内存大小不能超过此硬件逻辑限制,就算物理内存富余,如果应用超过VM最大内存,就会出现内存溢出crash。
由程序控制操作的内存空间在heap上,分java heapsize 和native heapsize。
- java申请的内存在vm heap上,所以如果java申请的内存大小超过vm的逻辑内存限制,就会出现内存溢出的异常。
- native层内存申请不受其限制,native层受native process对内存大小的限制。
查看App内存限制
adb shell
cat /system/build.prop
通过代码获取
ActivityManager activityManager = (ActivityManager)getBaseContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
activityManager.getMemoryClass();
ams为什么能获取:因为 ams.setsystemprocess中的meminfobinder调用底层内存信息
app:内存 大小 每个厂商机型给初始分配内存不同 源码中有两个地方可以修改
1./frameworks/base/core/jni/AndroidRuntime.cpp
parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf, "-Xms", "4m");
parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx", "16m"); //修改这里
- 安卓10以下/system/core/init/init.cpp
OOM解决问题 :主动获取了解当前app的内存使用情况
低内存杀进程机制
OOM Killer
oom_adj
app运行时,点击home app挂到后台 不会立马被系统杀掉,开多应用挂后台,安卓系统给应用分级
数字越大越容易被系统杀掉,粗粒度
app: 前台可见 oom_adj 0,后台2
oom_score_adj [-1000,1000] 更细粒度杀进程
adb shell下使用 cat /proc/进程号/oom_adj即可查询该应用的oom_adj值
若两个应用a 和b oom_adj值都是11 则谁占用内存多杀谁
所以要尽量降低应用进入后台的内存
内存三大问题
1.内存抖动
内存波动图呈锯齿状,GC导致卡顿
在AS中的Profiler中使用查看波动图,内存抖动会频繁GC,如自定义view中的ondraw()中频繁创建销毁画笔路径等,最后会导致内存千疮百孔
2.内存泄漏
在当前应用周期内不再使用的对象被GC Roots引用,导致不能回收,使实际可使用内存变小
产生泄漏之后不会立马导致应用崩溃,1个地方存在泄漏,程序跑久了之后,内存泄漏会一直积攒着,达到OOM级别,会导致应用低内存,会导致发热,耗电更严重的导致内存溢出
**注:**OOM不是只代表Java堆内存溢出。还因为无足够连续内存空间:FD(文件句柄,数字)数量超出限制;线程数量超出限制;虚拟内存不足;
3.内存溢出
即OOM,OOM时会导致程序异常。Android设备出厂后,Java虚拟机对单个应用的最大分配内存就确定下来了,超出这个值就会OOM
常见分析内存问题的命令
常用内存调优分析命令:
- dumpsys meminfo
内存指标概念:
Item | 全称 | 含义 | 等价 |
---|---|---|---|
USS | Unique Set Size | 物理内存 | 进程独占的内存 |
PSS | Proportional Set Size | 物理内存 | PSS=USS+按比例包含共享库 |
RSS | Resident Set Size | 物理内存 | RSS=USS+包含共享库 |
VSS | Virtual Set Size | 虚拟内存 | VSS=RSS+未分配实例物理内存 |
总结:VSS>=RSS>=PSS>=USS,但/dev/kgsl-3d0部分必须考虑VSS 看的时候主要看PSS
dumpsys meminfo --package + 包名可以查看具体应用的内存情况
- procrank
- cat /proc/meminfo
- free
- showmap
- vmstat
- top -n 1
总结:
- dumpsys meminfo适用场景:查看进程的oom_adj,或者dalvik/native等区域内存情况,或者某个进程或apk的内存情况,功能非常强大
- procrank:查看进程的VSS/RSS/PSS/USS各个内存指标
- cat /proc/meminfo 查看系统的详尽内存信息,包含内核情况
- free适用场景:只查看系统的可用内存
- showmap 查看进程的虚拟地址控件的内存分配情况
- vmstat 周期性地打印出进程运行队列,系统切换 ,CPU时间占比等情况
MAT性能分析工具:MemoryAnalyzer Tools
- incoming references
被持有
- outgoing references
持有
hprof-conv xxx.hprof 转一下即可用MAT打开内存快照
- Shallow Heap 浅堆
单独自己的占用大小
- Retained Heap 深堆
总计引用占用大小