内存管理概览:ART堆结构、Region空间与TLAB、内存分配流程
内存管理,说白了就是ART的"后勤部"。Java程序跑得再欢,对象总得有个地方放吧?GC再厉害,也得知道哪些内存能回收。今天我们就来聊聊ART堆的骨架——堆结构、Region空间、TLAB,以及对象是怎么一步步分配进来的。
一、ART堆的整体结构
ART的堆不像JVM那样分新生代、老年代。它更简洁,但也更灵活。我个人习惯把ART堆看成三块区域:
- Zygote空间:系统启动时预加载的类、资源都在这。这块区域几乎不动,所有子进程共享。
- 分配空间(Allocation Space):应用自己创建的对象,大部分都落在这里。
- 大对象空间(Large Object Space):超过一定阈值的大数组,比如Bitmap的像素数据,直接扔到这里。
嗯,这里要注意:Zygote空间是只读的,GC不会去动它。你想想看,如果GC去扫描Zygote空间,那所有进程都得跟着遭殃。
核心要点:ART堆的地址空间是连续的,但逻辑上分为多个Region。每个Region有自己的用途和GC策略。
二、Region空间——ART的"格子间"
从Android 8.0开始,ART引入了Region-based堆管理。说白了,就是把堆切成一个个固定大小的Region(默认256KB)。每个Region都有自己的状态:
| Region状态 | 说明 |
|---|---|
| kRegionStateAllocated | 已分配,正在使用 |
| kRegionStateFree | 空闲,可回收 |
| kRegionStateLargeObject | 用于大对象 |
| kRegionStateReclaim | 标记为可回收 |
为什么用Region?我在项目中遇到过一个问题:老版本ART做并发标记时,需要遍历整个堆,卡顿很明显。Region化之后,GC可以按Region粒度来操作,局部性更好,停顿时间也降下来了。
小技巧:调试时可以打印Region信息,看看哪些Region被频繁分配,哪些一直空闲。这对优化内存分配很有帮助。
三、TLAB——线程的"私房钱"
TLAB(Thread Local Allocation Buffer)这个概念,搞过JVM的应该不陌生。ART里也有,而且实现得更轻量。
每个线程在堆里预申请一小块内存(默认256KB),作为自己的TLAB。线程创建对象时,优先从TLAB里分配。TLAB用完了,再去堆里申请新的。
为什么要这么做?你想想看,如果所有线程都去抢堆的全局锁,那并发一高,分配效率就崩了。TLAB让每个线程有自己的"私房钱",互不干扰。
// ART中TLAB分配的简化逻辑
void* TLAB::Alloc(size_t size) {
if (free_end_ - free_begin_ >= size) {
void* ptr = free_begin_;
free_begin_ += size;
return ptr;
}
// TLAB空间不足,重新申请
RefillTLAB(size);
return Alloc(size);
}
我曾经调试过一个性能问题:某个线程频繁创建小对象,TLAB很快就用完了,导致频繁触发全局分配。后来把TLAB大小调大到512KB,性能提升很明显。
注意:TLAB不是越大越好。太大了会浪费内存,太小了又频繁触发全局分配。一般256KB到512KB是比较合理的范围。
四、内存分配流程——一个对象的"出生记"
一个Java对象从new到真正落地,经历了哪些步骤?我画了一张图,你看完就明白了。
流程其实不复杂:
- 计算对象大小:包括对象头、实例字段、对齐填充。ART的对象头是8字节(32位)或12字节(64位)。
- 检查TLAB:如果当前线程的TLAB还有空间,直接分配。这是最快路径。
- TLAB不足时:走全局分配路径。ART会尝试从堆的Free Region中拿一块,或者触发一次小GC。
- 分配成功:返回对象指针,设置对象头信息。
- 对象初始化:调用构造函数,设置字段默认值。
我记得有一次线上OOM,排查下来发现是某个线程的TLAB设置太小,频繁触发全局分配,导致GC压力剧增。调大TLAB后,问题就解决了。
关键点:TLAB分配是ART中最快的分配路径,没有之一。尽量让对象分配走TLAB,能大幅提升性能。
五、避坑指南
做内存优化这么多年,我踩过不少坑。分享几个常见的:
- 大对象不要走TLAB:超过TLAB剩余空间的对象,会直接走全局分配。如果你频繁创建大对象,建议考虑对象池。
- TLAB大小要合理:太小了频繁触发全局分配,太大了浪费内存。我一般建议256KB起步,根据线程分配频率动态调整。
- 注意Zygote空间:Zygote空间是只读的,不要试图修改它。否则你会看到奇怪的crash。
调试技巧:用adb shell dumpsys meminfo可以查看堆的Region分布。重点关注Allocated和Free的比例,如果Free太少,说明内存压力大。
好了,这一章就到这里。内存管理是ART的基石,理解堆结构、Region和TLAB,后面讲GC和并发标记时你会轻松很多。
公众号:蓝海资料掘金营,微信deep3321