8、ART内存分配机制:RosAlloc分配器,Bump Pointer分配器,大对象分配策略

好,咱们接着聊ART的内存分配。前面讲了Dalvik和ART的整体区别,现在该深入到ART内部,看看它到底怎么给对象分配内存的。

ART里主要有三种分配器:RosAllocBump Pointer,还有专门处理大对象的策略。这三种东西,说白了就是ART应对不同场景的三板斧。

8.1 RosAlloc分配器:老大哥,稳

RosAlloc,全称是“Runs-of-Slots Allocator”。名字挺绕口,但它的设计思路其实很朴素——解决碎片化问题

我在项目里遇到过好几次,应用跑着跑着就OOM了,但看总内存明明还有不少空闲。后来一查,全是内存碎片搞的鬼。RosAlloc就是为了对付这种情况。

核心思想:RosAlloc把堆内存划分成多个“run”(运行块),每个run里又分成固定大小的“slot”(槽位)。分配时,找对应大小的run,从里面拿一个slot出来。释放时,把slot还回去。

这样做的好处很明显:

  • 碎片少:因为都是固定大小,不会出现小对象卡在大对象中间的情况
  • 分配快:找空闲slot就是查个位图的事,O(1)复杂度
  • 回收快:释放时直接标记位图,不需要合并相邻空闲块

但RosAlloc也有个缺点——内部碎片。比如你申请了18字节,它可能给你一个32字节的slot,多出来的14字节就浪费了。嗯,这是固定大小分配器的通病。

8.2 Bump Pointer分配器:快,但短命

Bump Pointer,翻译过来就是“碰撞指针”。这个名字很形象——它就像一把尺子,每次分配就把指针往后“撞”一下。

它的工作流程简单到令人发指:

  1. 维护一个指针,指向当前空闲区域的起始位置
  2. 要分配N字节,就把指针往后移动N字节
  3. 返回移动前的位置

你看,就三步。没有链表查找,没有位图扫描,没有碎片整理。所以它极快,比RosAlloc还要快一个数量级。

我个人的习惯:在分析性能问题时,如果发现大量小对象的分配和回收,我会先看看Bump Pointer的命中率。如果命中率低,说明很多对象逃逸到了老年代,这时候就该考虑优化代码了。

但Bump Pointer有个致命弱点——只能用于连续分配,不能单独释放。你想想看,如果分配了A、B、C三个对象,你把B释放了,指针怎么处理?它没法退回去,因为后面还有C。

所以Bump Pointer通常用于临时对象分配区(比如TLAB,Thread-Local Allocation Buffer)。这些对象生命周期短,等线程用完,整个区域一次性回收,指针直接复位。

8.3 大对象分配策略:特殊对象,特殊对待

什么叫大对象?在ART里,通常指超过12KB的对象。比如大的Bitmap、ByteBuffer、数组等。

大对象不走RosAlloc,也不走Bump Pointer。为什么?

  • RosAlloc的slot最大也就几KB,装不下
  • Bump Pointer虽然装得下,但大对象生命周期长,放TLAB里不合适

所以ART给大对象开了个专用通道——直接通过mmap从系统申请内存。这些内存不参与GC的常规回收,而是由专门的大对象空间(Large Object Space,LOS)管理。

我曾经踩过一个坑:有个同事在循环里创建Bitmap,每次都是大对象。结果LOS里的对象越来越多,GC回收时发现这些对象还在引用,没法释放。最后堆内存暴涨,直接OOM。后来改成复用Bitmap,问题就解决了。

大对象分配策略总结一下:

特性 说明
触发条件 对象大小 > 12KB(不同版本阈值略有差异)
分配方式 mmap直接映射,不经过堆内存
回收方式 由LOS GC专门处理,标记-清除算法
优点 避免大对象在堆内造成碎片
缺点 分配和回收开销较大

8.4 三种分配器的协作流程

说了这么多,这三种分配器到底怎么配合工作的?我画了张图,你看一眼就明白了。

ART内存分配流程 对象分配请求 是否大对象? 大对象空间(LOS) mmap直接分配 TLAB可用? Bump Pointer TLAB内快速分配 RosAlloc 堆内存中找合适slot 大对象 TLAB 堆内存

流程其实很清晰:

  1. 收到分配请求,先判断是不是大对象。是的话直接走LOS
  2. 不是大对象,再看当前线程的TLAB有没有空间。有的话Bump Pointer一把梭,快得很
  3. TLAB不够了,才去堆内存找RosAlloc分配

这个优先级设计很有意思。你想想看,大部分对象都是小对象,而且生命周期短。用Bump Pointer处理这些小对象,效率最高。只有那些“漏网之鱼”才交给RosAlloc兜底。

避坑指南:我曾经优化过一个应用,发现GC频繁得离谱。用工具一看,TLAB命中率只有30%。也就是说70%的对象都没能在TLAB里分配,全跑到RosAlloc去了。后来调整了TLAB大小,命中率升到85%,GC次数直接降了一半。

8.5 小结

ART的三种分配器,各有各的定位:

  • RosAlloc:堆内存的主力,解决碎片问题,适合各种大小的对象
  • Bump Pointer:TLAB里的快枪手,只处理临时小对象
  • 大对象策略:特殊通道,避免大对象污染堆内存

理解这些分配机制,对排查内存问题特别有帮助。比如你发现应用频繁GC,可以先看看是不是TLAB命中率太低。如果是,调大TLAB或者优化对象分配模式,往往立竿见影。

嗯,这一章就到这里。下一章我们聊聊GC的几种算法,看看ART是怎么回收这些分配出去的内存的。


公众号:蓝海资料掘金营,微信deep3321