关于虚拟机的参数的调整 --- heapgrowthlimit/heapsize的配置
1. dalvik.vm.heapgrowthlimit和dalvik.vm.heapsize是什么
heapgrowthlimit/heapsize
是虚拟机用量的约束,如下面Dalvik的用量会受到这个限制
adb shell dumpsys meminfo $(adb shell pidof com.android.systemui)
Applications Memory Usage (in Kilobytes):
Uptime: 6856858 Realtime: 6856858
** MEMINFO in pid 15255 [com.android.systemui] **
Pss Private Private SwapPss Rss Heap Heap Heap
Total Dirty Clean Dirty Total Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------
Native Heap 2505 2484 0 12094 2696 16664 15365 1298
Dalvik Heap 8867 8840 0 2210 8992 16496 8248 8248
Dalvik Other 1620 1500 0 1260 2060
Stack 452 452 0 524 452
Ashmem 0 0 0 0 16
Other dev 32 0 8 0 1448
.so mmap 1696 96 792 202 16012
.jar mmap 1719 0 632 0 19528
.apk mmap 1943 0 1344 0 16724
.ttf mmap 324 0 324 0 324
.dex mmap 3514 20 3492 16 3532
.oat mmap 945 0 252 0 11476
.art mmap 2949 2368 8 934 7548
Other mmap 139 4 72 0 1324
EGL mtrack 2015 2015 0 0 2015
GL mtrack 7140 7140 0 0 7140
Unknown 220 220 0 400 260
TOTAL 53720 25139 6924 17640 53720 33160 23613 9546
目前基本上使用Android原生配置的这个值,很少应用实际用量会由于超过这个值的约束,一般都是由于gl/egl导致应用实体内存用量过大的问题。
除了实体用量约束,还有虚拟地址用量
的约束,虚拟地址用量
是这个值的2倍
=>
dalvik.vm.heapgrowthlimit
常规app使用的参数
dalvik.vm.heapsize
应用自己在AndroidManifest.xml
设置了android:largeHeap="true"
,将会变成大应用的设置
如大应用虚拟内存virtual size
是dalvik.vm.heapsize
的2倍,常规应用virtual size
是dalvik.vm.heapgrowthlimit
的2倍
$ adb shell getprop | grep "dalvik.vm.heap"
[dalvik.vm.heapgrowthlimit]: [128m]
[dalvik.vm.heapsize]: [256m]
adb shell showmap $(adb shell pidof system_server) | grep "dalvik-main"
virtual shared shared private private
size RSS PSS clean dirty clean dirty swap swapPSS # object
524288 8196 8196 0 0 0 8196 17884 17884 1 [anon:dalvik-main space (region space)]
adb shell showmap $(adb shell pidof com.android.vending) | grep "dalvik-main"
virtual shared shared private private
size RSS PSS clean dirty clean dirty swap swapPSS # object
262144 3776 3776 0 0 0 3776 4 4 1 [anon:dalvik-main space (region space)]
2. android:largeHeap如何影响初始设置
在包解析parseBaseApplication
的时候,会设置ApplicationInfo.FLAG_LARGE_HEAP
区别普通应用
//frameworks/base/core/java/android/content/pm/PackageParser.java
private boolean parseBaseApplication(Package owner, Resources res,
//...
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_largeHeap,
false)) {
ai.flags |= ApplicationInfo.FLAG_LARGE_HEAP;
}
接下去在进程启动handleBindApplication
的时候,会判断这个值,分别调用clearGrowthLimit
(大应用调用) clampGrowthLimit
(常规应用调用)
//frameworks/base/core/java/android/app/ActivityThread.java
private void handleBindApplication(AppBindData data) {
//...
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.
dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
}
于是就会影响虚拟机初始设置,至于clearGrowthLimit
、clampGrowthLimit
这2个方法,后面一起讲解
3. 遇到过需要动这个参数的问题
之前遇到过项目中system_server
出现native error(NE)
,原因是Out of memory
,当时查看system_server实体用量其实不大,但是为什么这个项目比较容易遇到Out of memory
呢,后面调查发现,其实是虚拟内存用量爆掉了。
VmPeak
表示进程所占用最大虚拟内存大小,VmSize
表示进程当前虚拟内存大小
VmPeak:3012615 kB
VmSize:2614240 kB
可以看到system_server
进程最大的虚拟内存用量都到3G
了,一般32bit
的设备分配给虚拟内存的地址空间最大是3G
(CPU的地址总线的是32位的,可寻址范围2^32(4G),高1G的空间为内核空间,低3G的空间为用户空间
);64bit
的设备一般就不会出现这种问题,虚拟地址空间充裕得很。问题主要集中在32bit
的设备上。
既然找到问题了,那么我们看一下map里面的地址是否如此
virtual shared shared private private
size RSS PSS clean dirty clean dirty swap swapPSS # object
-------- -------- -------- -------- -------- -------- -------- -------- -------- ---- ------------------------------
1048576 7808 7808 0 0 0 7808 0 0 5 [anon:dalvik-main space (region space)]
该项目dalvik
虚拟地址空间已经到了1G
(当然就算是1G
,也不应该爆掉,这里面还跟别的虚拟用量有关系,这里仅仅是针对anon:dalvik-main space (region space))
继续查看dalvik
的属性
[dalvik.vm.heapgrowthlimit]: [256m]
[dalvik.vm.heapsize]: [512m]
发现heapsize确实比较大,512m
针对anon:dalvik-main space (region space))
这类较大的一般都建议在32bit
上进行调整,不然留给app申请其它虚拟内存的范围就很少了
如调整减半
[dalvik.vm.heapgrowthlimit]: [128m]
[dalvik.vm.heapsize]: [256m]
如下anon:dalvik-main space (region space))
就只有512m的占用了,初始消耗虚拟内存是VmSize是 1.53G左右
,可以有较充裕的空间给到其它功能。32bit
上系统虚拟内存,开机建议控制在1.4-1.7G
之间
virtual shared shared private private
size RSS PSS clean dirty clean dirty swap swapPSS # object
-------- -------- -------- -------- -------- -------- -------- -------- -------- ---- ------------------------------
1537736 200828 75456 125264 17688 37652 20224 14876 14876 3235 TOTAL
524288 7060 7060 0 0 0 7060 0 0 13 [anon:dalvik-main space (region space)]
4. 从问题入手,我们接着探究一下这个是怎么影响到region space的
1、在AndroidRuntime
启动虚拟机的时候,会根据系统属性,传入虚拟机,如dalvik.vm.heapsize
传入的标识是-Xmx
,dalvik.vm.heapgrowthlimit
是-XX:HeapGrowthLimit=
frameworks/base/core/jni/AndroidRuntime.cpp
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote) {
//...
parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx", "16m");
parseRuntimeOption("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");
2、在art的代码parsed_options.cc
(解析虚拟机参数的地方)直接搜索刚才的-Xmx
、XX:HeapGrowthLimit=
就能得到虚拟机对应的名字
//art/runtime/parsed_options.cc
.Define("-Xmx_")
.WithType<MemoryKiB>()
.IntoKey(M::MemoryMaximumSize) //对应dalvik.vm.heapsize
.Define("-XX:HeapGrowthLimit=_")
.WithType<MemoryKiB>()
.IntoKey(M::HeapGrowthLimit) //对应dalvik.vm.heapgrowthlimit
3、在runtime.cc
中这些参数都是用来构建虚拟机的gc::Heap
//art/runtime/runtime.cc
bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
//...
heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize),
runtime_options.GetOrDefault(Opt::HeapGrowthLimit), //dalvik.vm.heapgrowthlimit, HeapGrowthLimit是第二个参数
runtime_options.GetOrDefault(Opt::HeapMinFree),
runtime_options.GetOrDefault(Opt::HeapMaxFree),
runtime_options.GetOrDefault(Opt::HeapTargetUtilization),
foreground_heap_growth_multiplier,
runtime_options.GetOrDefault(Opt::StopForNativeAllocs),
runtime_options.GetOrDefault(Opt::MemoryMaximumSize), //dalvik.vm.heapsize, MemoryMaximumSize是第8个参数
runtime_options.GetOrDefault(Opt::NonMovingSpaceCapacity),
GetBootClassPath(),
GetBootClassPathLocations(),
image_location_,
instruction_set_,
// Override the collector type to CC if the read barrier config.
kUseReadBarrier ? gc::kCollectorTypeCC : xgc_option.collector_type_, //gc type,注意此处android R使用的是kCollectorTypeCC
kUseReadBarrier ? BackgroundGcOption(gc::kCollectorTypeCCBackground)
: runtime_options.GetOrDefault(Opt::BackgroundGc),
/* ... */
4、heap.cc
里面会用到这些值,如growth_limit_
初始值就是dalvik.vm.heapgrowthlimit
,capacity_
初始值就是dalvik.vm.heapsize
,同时在构造函数里面可以看到RegionSpace
默认创建就是按照2倍的capacity_
来创建的MemMap region_space_mem_map = space::RegionSpace::CreateMemMap(kRegionSpaceName, capacity_ * 2, request_begin);
,好的现在知道默认虚拟机大小是2倍
的dalvik.vm.heapsize
//art/runtime/gc/heap.cc
Heap::Heap(size_t initial_size,
size_t growth_limit,//dalvik.vm.heapgrowthlimit
size_t min_free,
size_t max_free,
double target_utilization,
double foreground_heap_growth_multiplier,
size_t stop_for_native_allocs,
size_t capacity,//dalvik.vm.heapsize
size_t non_moving_space_capacity,
const std::vector<std::string>& boot_class_path,
const std::vector<std::string>& boot_class_path_locations,
const std::string& image_file_name,
const InstructionSet image_instruction_set,
CollectorType foreground_collector_type,
CollectorType background_collector_type,
/* ... */)
: /* ... */
foreground_collector_type_(foreground_collector_type),
background_collector_type_(background_collector_type),
/* ... */
capacity_(capacity),
growth_limit_(growth_limit),
/* ... */
if (foreground_collector_type_ == kCollectorTypeCC) {
MemMap region_space_mem_map =
space::RegionSpace::CreateMemMap(kRegionSpaceName, capacity_ * 2, request_begin);
5、直接在第2章节
里面已经讲过handleBindApplication
的时候,会分别调用clearGrowthLimit
(大应用调用) clampGrowthLimit
(常规应用调用),那逐一来看一下
=> clearGrowthLimit
,将growth_limit_
设置成capacity_
都用dalvik.vm.heapsize
,同时调用malloc_space->ClearGrowthLimit
清除growth_limit_
之前的设置,重新设定为GetMemMap()->Size()
//art/runtime/gc/heap.cc
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());
}
}
//art/runtime/gc/space/malloc_space.h
// Removes the fork time growth limit on capacity, allowing the application to allocate up to the
// maximum reserved size of the heap.
void ClearGrowthLimit() {
growth_limit_ = NonGrowthLimitCapacity();
}
// The total amount of memory reserved for the alloc space.
size_t NonGrowthLimitCapacity() const override {
return GetMemMap()->Size();
}
=> clampGrowthLimit
中会将capacity_
设置成growth_limit_
,也就是都变成dalvik.vm.heapgrowthlimit
的值,同时这里会更改region_space_
的大小region_space_->ClampGrowthLimit(2 * capacity_);
为dalvik.vm.heapgrowthlimit
的2倍
void Heap::ClampGrowthLimit() {
// Use heap bitmap lock to guard against races with BindLiveToMarkBitmap.
ScopedObjectAccess soa(Thread::Current());
WriterMutexLock mu(soa.Self(), *Locks::heap_bitmap_lock_);
capacity_ = growth_limit_;
for (const auto& space : continuous_spaces_) {
if (space->IsMallocSpace()) {
gc::space::MallocSpace* malloc_space = space->AsMallocSpace();
malloc_space->ClampGrowthLimit();
}
}
if (collector_type_ == kCollectorTypeCC) {
DCHECK(region_space_ != nullptr);
// Twice the capacity as CC needs extra space for evacuating objects.
region_space_->ClampGrowthLimit(2 * capacity_);
}
// This space isn't added for performance reasons.
if (main_space_backup_.get() != nullptr) {
main_space_backup_->ClampGrowthLimit();
}
}
region_space
的ClampGrowthLimit
会重新设定GetMemMap
的size
//art/runtime/gc/space/region_space.cc
void RegionSpace::ClampGrowthLimit(size_t new_capacity) {
MutexLock mu(Thread::Current(), region_lock_);
CHECK_LE(new_capacity, NonGrowthLimitCapacity());
size_t new_num_regions = new_capacity / kRegionSize;
if (non_free_region_index_limit_ > new_num_regions) {
LOG(WARNING) << "Couldn't clamp region space as there are regions in use beyond growth limit.";
return;
}
num_regions_ = new_num_regions;
if (kCyclicRegionAllocation && cyclic_alloc_region_index_ >= num_regions_) {
cyclic_alloc_region_index_ = 0u;
}
SetLimit(Begin() + new_capacity);
if (Size() > new_capacity) {
SetEnd(Limit());
}
GetMarkBitmap()->SetHeapSize(new_capacity);
GetMemMap()->SetSize(new_capacity);
}
阿里的有个代码也主要是为了在app中可以调用ClampGrowthLimit
,来避免阿里系的应用由于设置了largeHeap
导致的阿里系oom频繁的问题(32bit系统中),为啥这个大了会导致oom,具体可以查看章节3
至此这2个参数应该已经解释的比较清楚了,后面的文章会讲解其它虚拟机参数如何配置,和它的作用