Android中每个App默认情况下是运行在一个独立进程中的, 而这个独立进程正是从Zygote孵化出来的VM进程, 也就是说, 也就是说每个Android APP在运行时会启动一个Java虚拟机。
并且系统会给它分配固定的内存空间(手机厂商会根据手机的配置情况来对其进行调整)。
Android是一个多任务系统, 为了保证多任务的运行, Android给每个App可使用的Heap大小设定了一个限定值.
这个值是系统设置的prop值, 保存在System/build.prop
文件中. 一般国内的手机厂商都会做修改, 根据手机配置不同而不同, 可以直接打开查看与修改。
其中和虚拟机内存相关的主要有以下三个:
1 . dalvik.vm.heapstartsize
– App启动后,系统分配给它的Heap初始大小,随着App使用可增加。
2 . dalvik.vm.heapgrowthlimit
– 默认情况下, App可使用的Heap的最大值, 超过这个值就会产生OOM.
3 . dalvik.vm.heapsize
– 如果App的manifest文件中配置了largeHeap属性, 那么App可使用的Heap的最大值为此项设定值。
<application
android:largeHeap="true">
...
</application>
所以对于同一个手机,不开启largeHeap
属性时与多进程时,每个APP的虚拟机分配的内存的上限都是heapgrowthlimit
。
Android在ActivityManager类中提供了API可以运行时获取这些属性值,如下:
//ActivityManager的getMemoryClass()获得内用正常情况下内存的大小,即heapgrowthlimit的值
//ActivityManager的getLargeMemoryClass()可以获得开启largeHeap最大的内存大小,即heapsize的指
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.getMemoryClass();
activityManager.getLargeMemoryClass();
虚拟机分配内存的具体源码可以AOSP的Heap.cpp文件中查看:
/* Try as hard as possible to allocate some memory.
*/
static void *tryMalloc(size_t size)
{
void *ptr;
//TODO: figure out better heuristics
// There will be a lot of churn if someone allocates a bunch of
// big objects in a row, and we hit the frag case each time.
// A full GC for each.
// Maybe we grow the heap in bigger leaps
// Maybe we skip the GC if the size is large and we did one recently
// (number of allocations ago) (watch for thread effects)
// DeflateTest allocs a bunch of ~128k buffers w/in 0-5 allocs of each other
// (or, at least, there are only 0-5 objects swept each time)
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
/*
* The allocation failed. If the GC is running, block until it
* completes and retry.
*/
if (gDvm.gcHeap->gcRunning) {
/*
* The GC is concurrently tracing the heap. Release the heap
* lock, wait for the GC to complete, and retrying allocating.
*/
dvmWaitForConcurrentGcToComplete();
} else {
/*
* Try a foreground GC since a concurrent GC is not currently running.
*/
gcForMalloc(false);
}
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
/* Even that didn't work; this is an exceptional state.
* Try harder, growing the heap if necessary.
*/
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
size_t newHeapSize;
newHeapSize = dvmHeapSourceGetIdealFootprint();
//TODO: may want to grow a little bit more so that the amount of free
// space is equal to the old free space + the utilization slop for
// the new allocation.
LOGI_HEAP("Grow heap (frag case) to "
"%zu.%03zuMB for %zu-byte allocation",
FRACTIONAL_MB(newHeapSize), size);
return ptr;
}
/* Most allocations should have succeeded by now, so the heap
* is really full, really fragmented, or the requested size is
* really big. Do another GC, collecting SoftReferences this
* time. The VM spec requires that all SoftReferences have
* been collected and cleared before throwing an OOME.
*/
//TODO: wait for the finalizers from the previous GC to finish
LOGI_HEAP("Forcing collection of SoftReferences for %zu-byte allocation",
size);
gcForMalloc(true);
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
return ptr;
}
//TODO: maybe wait for finalizers and try one last time
LOGE_HEAP("Out of memory on a %zd-byte allocation.", size);
//TODO: tell the HeapSource to dump its state
dvmDumpThread(dvmThreadSelf(), false);
return NULL;
}
具体流程如下:
所以产生OOM时,一定是java的堆中 已有的内存 + 申请的内存 >= heapgrowthlimit导致的,不会因为手机目前物理内存是否紧张而改变 - 当物理内存非常紧张时系统会通过LowMemory Killer杀掉一些低优先级的进程。
相应的,物理内存非常充足的情况也会有OOM的情况发生。
当APP出现OOM时,建议可以从以下两个方向来处理:
1 . 排查内存泄露问题
排查各个功能是否内存泄露情况,可以通过Android Studio中的MemoryMonitor功能进行分析,Memory Monitor也集成了HPROF Viewer和Allocation Tracker可以分析内存快照与内存分配追踪。另外推荐一个工具,square公司开源的leakcanary,非常简洁好用。
排查进程初始化时就直接申请并常驻内存的对象以及其他功能里申请的static对象或者单例对象的必要性。
2 . 内存优化
按照谷歌在youtube上发布的性能优化典范之内存篇,优化各功能的内存,或可参照 胡凯的总结 。大致有以下这些,具体请参见原文:
谨慎使用large heap
综合考虑设备的内存阈值与其他因素设计合适的缓存大小
onLowMemory与onTrimMemory
资源文件需要选择合适的文件夹进行存放
Try catch某些大内存分配的操作
谨慎使用static对象
特别留意单例对象中不合理的持有
珍惜Services资源
优化布局层次,减少内存消耗
谨慎使用“抽象”编程
使用nano protobufs序列化数据
谨慎使用依赖注入框架
谨慎使用多进程
使用ProGuard来剔除不需要的代码
谨慎使用第三方libraries
考虑不同的实现方式来优化内存占用
注意Activity的泄露
考虑使用Applicaiton Context代替Activity Context
注意临时Bitmap对象的及时回收
注意监听器的注销
注意缓存容器里的对象泄露
注意Webview的泄露
注意Cursor对象的及时关闭
复用系统自带的资源
ListView中对ConvertView的复用
Bitmap对象的复用
避免在ondraw方法里执行对象的创建
StringBuilder代替String
使用更加轻量的数据结构
避免在Android里使用enum
减少Bitmap对象的内存占用
使用更小的图片
减少对象的内存占用
内存对象的重复利用
避免对象的内存泄露
内存使用策略的优化
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8