内存优化
Java内存分配
- 方法区 ,虚拟机栈 本地方法栈
- 堆 程序计数器
- java内存回收算法
- 标记 -清除算法,标记需要回收的对象,然后进行回收
- 内存碎片化严重,内存不连续
- 复制算法,将内存分成两块 ,内存整理后把对象复制到另一块内存,避免了内存碎片化严重的问题
- 浪费了一块内存
- 标记 -整理算法
- 标记清除算法一样 ,但是整理后会把对方放在一块,避免了内存空洞,碎片化问题 ,也避免了复制算法的空间浪费
- 分代收集算法
- 新生代采用复制算法
- 老年代采用标记整理算法,因为存活率高
- 标记 -清除算法,标记需要回收的对象,然后进行回收
Android 内存管理机制
- 内存弹性分配,分配内存大小与厂商有关
- OOM : 内存真正不足 ,可用内存不足
Davlik和Art区别
- Dalvik回收算法固定
- Art回收算法可运行期选择,Art具备内存整理能力,避免内存碎片化,内存空洞
Low Memory Killer
- 进程分类 -可见进程 ,服务进程,后台进程,空进程
内存问题
- 内存抖动 : 锯齿状 ,GC导致卡顿
- 频繁创建对象,回收对象
- 内存泄漏 : 可用内存减少,频繁GC
- 内存中存在没有用的对象
- 频繁泄漏,导致内存抖动
- 内存溢出 : OOM.程序异常
工具使用
- Memory Profiler
- Memory Analyzer
- LeakCanary
- 查看应用内存使用情况 adb shell dumsys meminfo 包名或者pid
- 使用adb shell procrank
手机中的sh是经过精简过的,有些手机可能没有 procrank 命令,可以使用genymotion模拟器,或是自己安装procrank命令。使用procrank时,命令行的输出入下图:
可以看到,在linux下表示内存的耗用情况有四种不同的表现形式:
VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
VSS:VSS表示一个进程可访问的全部内存地址空间的大小。这个大小包括了进程已经申请但尚未使用的内存空间。在实际中很少用这种方式来表示进程占用内存的情况,用它来表示单个进程的内存使用情况是不准确的。
RSS:表示一个进程在RAM中实际使用的空间地址大小,包括了全部共享库占用的内存,这种表示进程占用内存的情况也是不准确的。
PSS:表示一个进程在RAM中实际使用的空间地址大小,它按比例包含了共享库占用的内存。假如有3个进程使用同一个共享库,那么每个进程的PSS就包括了1/3大小的共享库内存。这种方式表示进程的内存使用情况较准确,但当只有一个进程使用共享库时,其情况和RSS一模一样。
USS:表示一个进程本身占用的内存空间大小,不包含其它任何成分,这是表示进程内存大小的最好方式!
可以看到:VSS>=RSS>=PSS>=USS
- .其它常用命令命令:
adb shell kill PIDNumber 死你想杀死的后台进程来模拟某种 bug 的复现条件。
adb shell ps 查看当前终端中的进程信息
那么如何在代码中判断当前的硬件系统有多少的 RAM 呢?在 Framework ProcessList.java 中有如下代码可用:
ProcessList() {
MemInfoReader minfo = new MemInfoReader();
minfo.readMemInfo();
mTotalMemMb = minfo.getTotalSize()/(1024*1024);
}
Memory Analyzer
- 强大的java heap 分析工具,查找内存占用和泄漏
- 生成整体的报告,分析内存问题
- 线下深入使用
Memory Analyzer基本使用
实现方式
- 分析待机内存 .重点模块内存 . OOM率
- GC次数 GC时间
常规Dump Heap
- Debug.dumpHprofData();
LeakCanary分析
- build.gradle
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-3'
}
Bitmap内存模型
- API10之前像素放在在Native层,回收不及时
- API10之后放在堆内存
- API26之后放在Native内存
- getByteCount 获取bitmap占用内存
- 宽高一个像素占用内存大小(注意如果是放在资源文件还要乘以压缩比列)
ARTHook
- 挂钩,将额外的代码钩住原来的方法,修改执行逻辑
- 运行时插桩
- 性能分析
- Epic框架,Hook住一个java方法,使用范围4.0 - 9.0
- api ‘me.weishu:epic:0.3.6’
- 继承XC_MethodHook ,实现相应逻辑
- 注入Hook:DexposedBridge.findAndHookMethod
线上监控
- Debug.dumpHprofData();
- 当app内存占比超过80% ,dump内存文件 ,空闲时上传 ,文件比较大,上传失败率高
打开 LargeHeap
- 查看设备申请的内存大小
Runtime rt=Runtime.getRuntime();
long maxMemory=rt.maxMemory();
log.i("maxMemory:",Long.toString(maxMemory/(1024*1024)));
這個可以直接得到app可使用的最大memory size算出來是MB, 获得的是heapgrowthlimit
而dalvik.vm.heapsize是在manifest中设置了largeHeap=true 之后,可以使用的最大内存值
<application
.....
android:label="XXXXXXXXXX"
android:largeHeap="true">
.......
</application>
cat /system/build.prop
//读取这些值
dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=96m //未使用 android:largeHeap="false"
dalvik.vm.heapsize=384m //使用 android:largeHeap="true"
dalvik.vm.heaputilization=0.25
dalvik.vm.heapidealfree=8388608
dalvik.vm.heapconcurrentstart=2097152
ro.setupwizard.mode=OPTIONAL
ro.com.google.gmsversion=4.1_r6
net.bt.name=Android
dalvik.vm.stack-trace-file=/data/anr/traces.txt
谨慎使用SharedPreference
- 会把数据全部加载到内存 ,数据大消耗内存多
GC 日志解读
- 参考
D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/
9991K, external 4703K/5261K, paused 2ms 2ms
GC_XXX表明是哪类GC以及触发GC的原因。几种GC类型:
- GC_CONCURRENT:这是因为你的heap内存占用开始往上涨了,为了避免heap内存满了而触发执行的。
- GC_FOR_MALLOC:这是由于concurrent gc没有及时执行完而你的应用又需要分配更多的内存,内存要满了,这个时候不得不停下来进行malloc gc。
- GC_EXTERNAL_ALLOC:这是为external分配的内存执行的GC,也就是上文提到的Bitmap Pixel Data之类的。
- GC_HPROF_DUMP_HEAP:这是当你做HPROF这样一个操作去创建一个HPROF profile的时候执行的。
- GC_EXPLICIT:这是由于你显式的调用了System.gc(),这是不提倡的,一般来说我们可以信任系统的GC。
freed 2049K表明在这次GC中回收了多少内存。
65% free 3571K/9991K是heap的一些统计数据,表明这次回收后65%的heap可用,存活的对象大小3571K,heap大小是9991K。
external 4703K/5261K是Native Memory的数据。放Bitmap Pixel Data或者是NIO Direct Buffer之类的。第一个数字表明Native Memory中已分配了多少内存,第二个值有点类似一个浮动的阀值,表明分配内存达到这个值系统就会触发一次GC进行内存回收。
paused 2ms 2ms表明GC暂停的时间。从这里你可以看到越大的heap size你需要暂停的时间越长。如果是concurrent gc你会看到2个时间一个开始一个结束,这时间是很短的,但如果是其他类型的GC,你很可能只会看到一个时间,而这个时间是相对比较长的。