前言
本文基于Android 12 源码,通过SDK emulator单步调试分析java Heap 内存分配过程、OOM 判定规则、GC算法,通过表格的形式记录相关参数和简要逻辑,避免长篇大论贴源码带来的枯燥乏味。 我们先将dalvik相关参数放到前面,后面的分析经常用到这几个值:
[dalvik.vm.heapgrowthlimit]: [192m]
[dalvik.vm.heapmaxfree]: [8m]
[dalvik.vm.heapminfree]: [512k]
[dalvik.vm.heapsize]: [512m]
[dalvik.vm.heapstartsize]: [8m]
[dalvik.vm.heaptargetutilization]: [0.75]
Heap内存结构
内存分配器类型
ART中的内存分配器类型定义在allocator_type.h文件中
// Different types of allocators.
// Those marked with * have fast path entrypoints callable from generated code.
enum AllocatorType : char {
// BumpPointer spaces are currently only used for ZygoteSpace construction.
kAllocatorTypeBumpPointer, // Use global CAS-based BumpPointer allocator. (*)
kAllocatorTypeTLAB, // Use TLAB allocator within BumpPointer space. (*)
kAllocatorTypeRosAlloc, // Use RosAlloc (segregated size, free list) allocator. (*)
kAllocatorTypeDlMalloc, // Use dlmalloc (well-known C malloc) allocator. (*)
kAllocatorTypeNonMoving, // Special allocator for non moving objects.
kAllocatorTypeLOS, // Large object space.
// The following differ from the BumpPointer allocators primarily in that memory is
// allocated from multiple regions, instead of a single contiguous space.
kAllocatorTypeRegion, // Use CAS-based contiguous bump-pointer allocation within a region. (*)
kAllocatorTypeRegionTLAB, // Use region pieces as TLABs. Default for most small objects. (*)
};
不同的分配器对应不同的内存空间,并且有自身的内存分配方式。ART 中所涉及到的内存空间是用SpaceType来表示的,定义在space.h中:
enum SpaceType {
kSpaceTypeImageSpace,
kSpaceTypeMallocSpace,
kSpaceTypeZygoteSpace,
kSpaceTypeBumpPointerSpace,
kSpaceTypeLargeObjectSpace,
kSpaceTypeRegionSpace,
};
Heap初始化参数
heap 初始化时,会创建各种各样的space,通过heap 初始化代码来整理一下,下面是堆初始化的部分参数
成员变量 | 值 |
---|---|
foreground_collector_type_ | kCollectorTypeCC |
background_collector_type_ | kCollectorTypeCCBackground |
use_homogeneous_space_compaction_for_oom_ | false |
support_homogeneous_space_compaction | false |
separate_non_moving_space | true |
non_moving_space_capacity | kDefaultNonMovingSpaceCapacity = 64 * MB; |
heap_reservation_size | 4k |
capacity_ | 512M 即[dalvik.vm.heapsize]: [512m] |
large_object_space_type | LargeObjectSpaceType::kMap |
内存分配器内存空间对应关系
下面内容基于基于Android 12 SDK emulator,启动一个demo app分析:
内存分配器类型 | 内存空间 | 内存分配函数 | heap中对应的space 类型(Android 12 现状) | memory map中表示 |
---|---|---|---|---|
ImageSpace | MemMap | ImageSpace | 6f870000-6faca000 rw-p 00000000 00:00 0 [anon:dalvik-/apex/com.android.art/javalib/boot.art]6faca000-6fb0b000 rw-p 00000000 00:00 0 [anon:dalvik-/apex/com.android.art/javalib/boot-core-libart.art]6fb0b000-6fb31000 rw-p 00000000 00:00 0 [anon:dalvik-/apex/com.android.art/javalib/boot-okhttp.art] | |
kAllocatorTypeBumpPointer | BumpPointerSpace | AllocNonvirtual | ||
kAllocatorTypeRosAlloc | RosAllocSpace | Alloc 或者AllocNonvirtual | ||
kAllocatorTypeDlMalloc | DlMallocSpace | Alloc 或者AllocNonvirtual | "zygote / non moving space" | 71c14000-7231e000 rw-p 00000000 00:00 0 [anon:dalvik-zygote space] =======> 约7M7231e000-7231f000 rw-p 00000000 00:00 0 [anon:dalvik-non moving space]7231f000-72358000 rw-p 00000000 00:00 0 [anon:dalvik-non moving space]72358000-75415000 ---p 00000000 00:00 0 [anon:dalvik-non moving space]75415000-75c14000 rw-p 00000000 00:00 0 [anon:dalvik-non moving space] =======> 约57M |
kAllocatorTypeNonMoving | MallocSpace | Alloc | ||
kAllocatorTypeLOS | LargeObjectSpace | Alloc | "mem map large object space" 默认类型 LargeObjectSpaceType::kMap | 56c00000-56c04000 rw-p 00000000 00:00 0 [anon:dalvik-large object space allocation]56c04000-56c0c000 rw-p 00000000 00:00 0 [anon:dalvik-large object space allocation]56c0f000-56c3c000 rw-p 00000000 00:00 0 [anon:dalvik-large object space allocation]56c3c000-56c44000 rw-p 00000000 00:00 0 [anon:dalvik-large object space allocation] |
kAllocatorTypeRegion | RegionSpace | AllocNonvirtual | "main space (region space)" 大小 capacity_ * 2 虚拟内存 | 12c00000-52c00000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)] =====> 大小1G |
kAllocatorTypeTLAB | BumpPointerSpace | AllocWithNewTLAB 或者AllocTlab | ||
kAllocatorTypeRegionTLAB | RegionSpace | AllocWithNewTLAB 或者AllocTlab |
通过图片直观的感受下:
OOM 判定
我们看一下oom的判定函数:
inline bool Heap::IsOutOfMemoryOnAllocation(AllocatorType allocator_type,
size_t alloc_size,
bool grow) {
size_t old_target = target_footprint_.load(std::memory_order_relaxed);
while (true) {
size_t old_allocated = num_bytes_allocated_.load(std::memory_order_relaxed);
size_t new_footprint = old_allocated + alloc_size;
// Tests against heap limits are inherently approximate, since multiple allocations may
// race, and this is not atomic with the allocation.
if (UNLIKELY(new_footprint <= old_target)) {
return false;
} else if (UNLIKELY(new_footprint > growth_limit_)) {
return true;
}
// We are between target_footprint_ and growth_limit_ .
if (AllocatorMayHaveConcurrentGC(allocator_type) && IsGcConcurrent()) {
return false;
} else {
if (grow) {
if (target_footprint_.compare_exchange_weak(/*inout ref*/old_target, new_footprint,
std::memory_order_relaxed)) {
VlogHeapGrowth(old_target, new_footprint, alloc_size);
return false;
} // else try again.
} else {
return true;
}
}
}
}
这里需要解释几个变量的意思和关系
变量 | 意义 | 计算方式 |
---|---|---|
target_footprint_ | Target size (as in maximum allocatable bytes) for the heap. Weakly enforced as a limit for non-concurrent GC. Used as a guideline for computing concurrent_start_bytes_ in the concurrent GC case.target_footprint_ 是 ART 根据内存使用情况和 GC 策略动态计算的 堆内存目标容量,用于指导 GC 触发时机、内存分配失败判断以及堆空间扩展的决策 | 1. 初始化值为[dalvik.vm.heapstartsize]: [8m] 2. 再申请内存时,如果new_footprint在old_target和growth_limit_之间,且grow为true,将target_footprint_更新为new_footprint(增大)3. 在AMS中更新process state,如果满足条件将target_footprint_更新为min_foreground_target_footprint_(减小) |
num_bytes_allocated_ | Number of bytes currently allocated and not yet reclaimed. Includes active TLABS in their entirety, even if they have not yet been parceled out."当前已分配且尚未回收的字节数"(即活动对象占用的内存总量) | 1. 在AllocObjectWithAllocator函数会将申请的大小增加到num_bytes_allocated_ 2. gc时在函数RecordFree中将freed_bytes减去 |
growth_limit_ | he size the heap is limited to. This is initially smaller than capacity, but for largeHeap programs it is "cleared" making it the same as capacity. Only weakly enforced for simultaneous allocations.堆动态软上限 | 1. 初始化值为[dalvik.vm.heapgrowthlimit]: [192m]2. 如果应用在manifest里面指定了largeHeap,则默认设置为[dalvik.vm.heapsize]: [512m] if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();} |
综上,oom 判定的条件:
- num_bytes_allocated_ + alloc_size <= target_footprint_ 正常申请
- num_bytes_allocated_ + alloc_size > growth_limit_ OOM
- num_bytes_allocated_ + alloc_size 在target_footprint_ 和growth_limit_ 之间有以下三种情况:
- 满足AllocatorMayHaveConcurrentGC和IsGcConcurrent函数均为true,正常申请(Android12满足)
- 如不满足AllocatorMayHaveConcurrentGC和IsGcConcurrent函数均为true,且指定了grow为true,更新target_footprint_,正常申请
- 如不满足AllocatorMayHaveConcurrentGC和IsGcConcurrent函数均为true,且指定了grow为false,OOM
largeHeap 对应用的影响
androd 应用可以在manifest里面指定是否是largeHeap 应用,如下代码, 下面是谷歌官方的解释:
android:largeHeap="true"
如果应用在manifest中指定了largeHeap,PMS在进行包扫描的时候就会给app的applicationinfo中增加下面这个flag
/**
* Value for {@link #flags}: true when the application has requested a
* large heap for its processes. Corresponds to
* {@link android.R.styleable#AndroidManifestApplication_largeHeap
* android:largeHeap}.
*/
public static final int FLAG_LARGE_HEAP = 1<<20;
在handleBindApplication函数中会对这个flag有特殊处理:
if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
} else {
// Small heap, clamp to the current growth limit and let the heap release
// pages after the growth limit to the non growth limit capacity. b/18387825
dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
}
最终调用ART 中heap.cc中的ClearGrowthLimit 函数,这个重点将growth_limit_值调整, 上面的介绍中我们知道该值为192M,会更新为512M,
void Heap::ClearGrowthLimit() {
if (target_footprint_.load(std::memory_order_relaxed) == growth_limit_
&& growth_limit_ < capacity_) {
target_footprint_.store(capacity_, std::memory_order_relaxed);
concurrent_start_bytes_ =
UnsignedDifference(capacity_, kMinConcurrentRemainingBytes);
}
growth_limit_ = capacity_;
ScopedObjectAccess soa(Thread::Current());
for (const auto& space : continuous_spaces_) {
if (space->IsMallocSpace()) {
gc::space::MallocSpace* malloc_space = space->AsMallocSpace();
malloc_space->ClearGrowthLimit();
malloc_space->SetFootprintLimit(malloc_space->Capacity());
}
}
// This space isn't added for performance reasons.
if (main_space_backup_.get() != nullptr) {
main_space_backup_->ClearGrowthLimit();
main_space_backup_->SetFootprintLimit(main_space_backup_->Capacity());
}
}
结论:
系统设置 | 应用设置 | 堆上限 | ||
---|---|---|---|---|
[dalvik.vm.heapgrowthlimit]: [192m][dalvik.vm.heapsize]: [512m] | android:largeHeap=true | 512m | ||
仅设置[dalvik.vm.heapsize]: [512m] | 应用是否设置android:largeHeap=true 无影响,堆大小都是512m if (args.GetOrDefault(M::HeapGrowthLimit) <= 0u 或 args.GetOrDefault(M::HeapGrowthLimit) > args.GetOrDefault(M::MemoryMaximumSize)) {args.Set(M::HeapGrowthLimit, args.GetOrDefault(M::MemoryMaximumSize));} | 512m | ||
[dalvik.vm.heapgrowthlimit]: [192m][dalvik.vm.heapsize]: [512m] | android:largeHeap=false | 192m |
GC算法
GC的判断条件
在内存分配器申请内存的函数AllocObjectWithAllocator中,每次都会调用ShouldConcurrentGCForJava,这里面影响gc的因素只有两个值,我们从这两个值入手,解释下GC的算法
inline bool Heap::ShouldConcurrentGCForJava(size_t new_num_bytes_allocated) {
// For a Java allocation, we only check whether the number of Java allocated bytes excceeds a
// threshold. By not considering native allocation here, we (a) ensure that Java heap bounds are
// maintained, and (b) reduce the cost of the check here.
return new_num_bytes_allocated >= concurrent_start_bytes_;
}
new_num_bytes_allocated
new_num_bytes_allocated的计算在函数AllocObjectWithAllocator中,该函数是申请内存的入口函数:
concurrent_start_bytes_
每次GC的时候都会更新下一次GC的水位线concurrent_start_bytes_,入口在CollectGarbageInternal函数中,水位线的调整在函数中,为了便于记忆和理解,我们使用demo app在Adnroid 12 模拟器上单步调试分析,由于影响因素太多我们将其中涉及到的一些变量还是表格化显示,便于记忆和理解:
小结
好了我们总结一下gc水位线的计算过程
- 首先计算grow_bytes,根据heaptargetutilization计算出在目前的bytes_allocated的基础上需要增加多少(delta),要确保grow_bytes落在[512k,8m]中间
- target_size 计算,target_size等于grow_bytes乘以multiplier,后台1,前台3,目的是为了降低前台时的gc 频率,计算出来之后将target_size 通过函数SetIdealFootprint赋值给target_footprint_
- remaining_bytes计算,remaining_bytes是预留给gc期间申请内存用的,因为目前的gc是并发copy,remaining_bytes要保证落在[128k,512k]之间
- gc 水位线计算:concurrent_start_bytes_ = std::max(target_footprint - remaining_bytes, bytes_allocated)