dalvik
虚拟机是
Google
在
Android
平台上的
Java
虚拟机的实现,内存管理是
dalvik
虚拟机中的一个重要组件。
从概念上来说,内存管理的核心就是两个部分:分配内存和回收内存。 Java 语言使用 new 操作符来分配内存,但是与 C/C++ 等语言不同的是, Java 语言并没有提供任何操作来释放内存,而是通过一种叫做垃圾收集的机制来回收内存。对于内存管理的实现,我们通过三个方面来加以分析:内存分配,内存回收和内存管理调试。本文就是这一系列文章的第一篇,分析 dalvik 虚拟机是如何分配内存的。
1. 对象布局
内存管理的主要操作之一是为 Java 对象分配内存, Java 对象在虚拟机中的内存布局如下:
所有的对象都有一个相同的头部 clazz 和 lock 。
( 1 ) clazz:clazz 指向该对象的类对象,类对象用来描述该对象所属的类,这样可以很容易的从一个对象获取该对象所属的类的具体信息。
( 2 ) lock: 是一个无符号整数,用以实现对象的同步。
( 3 ) data: 存放对象数据,根据对象的不同数据区的大小是不同的。
2. 堆
堆是 dalvik 虚拟机从操作系统分配的一块连续的虚拟内存。 heapBase 是堆的起始地址, heapLimit 是堆的最大地址,堆大小的最大值可以通过 -Xmx 选项或 dalvik.vm.heapsize 指定。在原生系统中,一般 dalvik.vm.heapsize 值是 32M ,在 MIUI 中我们将其设为 64M 。
3. 堆内存位图
在虚拟机中维护了两个对应于堆内存的位图,称为 liveBits 和 markBits 。
在对象布局中,我们看到对象最小占用 8 个字节。在为对象分配内存时要求必须 8 字节对齐。这也就是说,对象的大小会调整为 8 字节的倍数。比如说一个对象的实际大小是 13 字节,但是在分配内存的时候分配 16 字节。因此所有对象的起始地址一定是 8 字节的倍数。堆内存位图就是用来描述堆内存的,每一个 bit 描述 8 个字节,因此堆内存位图的大小是对的 64 分之一。对于 MIUI 的实现来说,这两个位图各占 1M 。
liveBits 的作用是用来跟踪堆中以分配的内存,每分配一个对象时,对象的内存起始地址对应于位图中的位被设为 1 。在下一篇垃圾收集中我们会进一步的分析 liveBits 和 markBits 这两个位图的作用。
4. 堆的内存管理
在 dalvik 虚拟机实现中,是通过底层的 bionicC 库的 malloc/free 操作来分配 / 释放内存的。 bionicC 库的 malloc/free 操作是基于 DougLea 的实现 (dlmalloc) ,这是一个被广泛使用,久经考验的 C 内存管理库,本文不展开 dlmalloc 的具体实现,有兴趣的读者请参考 http://g.oswego.edu/dl/html/malloc.html 。
5. dvmAllocObject
在 dalvik 虚拟机中, new 操作符最终对应 dvmAllocObject 这个 C 函数。下面通过伪码的形式列出 dvmAllocObject 的实现。
Object*dvmAllocObject(ClassObject *clazz, int flags) {
n = get object size form class object clazz
first try: allocate n bytes from heap
if first try failed {
run garbage collector without collecting soft references
second try: allocate n bytes from heap
}
if second try failed {
third try: grow the heap and allocate n bytes from heap
( 注释:堆是虚拟内存,一开始并未分配所有的物理内存,只要还没有达到虚拟内存的最大值,可以通过获取更多物理内存的方式来扩展堆 )
}
if third try failed {
run garbage collector with collecting soft references
fourth try: grow the hap and allocate n bytes from heap
}
if fourth try failed, return null pointer, dalvik vm will abort
}
可以看出,为了分配内存,虚拟机尽了最大的努力,做了四次尝试。其中进行了两次垃圾收集,第一次不收集 SoftReference ,第二次收集 SoftReference 。从中我们也可以看出垃圾收集的时机,实质上在 dalvik 虚拟机实现中有 3 个时机可以触发垃圾收集的运行:
( 1 )程序员显式的调用 System.gc()
( 2 )内存分配失败时
( 3 )如果分配的对象大小超过 384KB ,运行并发标记 (concurrent mark) ,在下一篇文章中会介绍什么是并发标记
6. 小结
在 dalvik 虚拟机中,内存分配操作的流程相对比较简单直观,从一个堆中分配可用内存,分配失败时触发垃圾收集,接下来的文章中我们仔细分析 dalvik 虚拟机的垃圾收集。
从概念上来说,内存管理的核心就是两个部分:分配内存和回收内存。 Java 语言使用 new 操作符来分配内存,但是与 C/C++ 等语言不同的是, Java 语言并没有提供任何操作来释放内存,而是通过一种叫做垃圾收集的机制来回收内存。对于内存管理的实现,我们通过三个方面来加以分析:内存分配,内存回收和内存管理调试。本文就是这一系列文章的第一篇,分析 dalvik 虚拟机是如何分配内存的。
1. 对象布局
内存管理的主要操作之一是为 Java 对象分配内存, Java 对象在虚拟机中的内存布局如下:
所有的对象都有一个相同的头部 clazz 和 lock 。
( 1 ) clazz:clazz 指向该对象的类对象,类对象用来描述该对象所属的类,这样可以很容易的从一个对象获取该对象所属的类的具体信息。
( 2 ) lock: 是一个无符号整数,用以实现对象的同步。
( 3 ) data: 存放对象数据,根据对象的不同数据区的大小是不同的。
2. 堆
堆是 dalvik 虚拟机从操作系统分配的一块连续的虚拟内存。 heapBase 是堆的起始地址, heapLimit 是堆的最大地址,堆大小的最大值可以通过 -Xmx 选项或 dalvik.vm.heapsize 指定。在原生系统中,一般 dalvik.vm.heapsize 值是 32M ,在 MIUI 中我们将其设为 64M 。
3. 堆内存位图
在虚拟机中维护了两个对应于堆内存的位图,称为 liveBits 和 markBits 。
在对象布局中,我们看到对象最小占用 8 个字节。在为对象分配内存时要求必须 8 字节对齐。这也就是说,对象的大小会调整为 8 字节的倍数。比如说一个对象的实际大小是 13 字节,但是在分配内存的时候分配 16 字节。因此所有对象的起始地址一定是 8 字节的倍数。堆内存位图就是用来描述堆内存的,每一个 bit 描述 8 个字节,因此堆内存位图的大小是对的 64 分之一。对于 MIUI 的实现来说,这两个位图各占 1M 。
liveBits 的作用是用来跟踪堆中以分配的内存,每分配一个对象时,对象的内存起始地址对应于位图中的位被设为 1 。在下一篇垃圾收集中我们会进一步的分析 liveBits 和 markBits 这两个位图的作用。
4. 堆的内存管理
在 dalvik 虚拟机实现中,是通过底层的 bionicC 库的 malloc/free 操作来分配 / 释放内存的。 bionicC 库的 malloc/free 操作是基于 DougLea 的实现 (dlmalloc) ,这是一个被广泛使用,久经考验的 C 内存管理库,本文不展开 dlmalloc 的具体实现,有兴趣的读者请参考 http://g.oswego.edu/dl/html/malloc.html 。
5. dvmAllocObject
在 dalvik 虚拟机中, new 操作符最终对应 dvmAllocObject 这个 C 函数。下面通过伪码的形式列出 dvmAllocObject 的实现。
Object*dvmAllocObject(ClassObject *clazz, int flags) {
n = get object size form class object clazz
first try: allocate n bytes from heap
if first try failed {
run garbage collector without collecting soft references
second try: allocate n bytes from heap
}
if second try failed {
third try: grow the heap and allocate n bytes from heap
( 注释:堆是虚拟内存,一开始并未分配所有的物理内存,只要还没有达到虚拟内存的最大值,可以通过获取更多物理内存的方式来扩展堆 )
}
if third try failed {
run garbage collector with collecting soft references
fourth try: grow the hap and allocate n bytes from heap
}
if fourth try failed, return null pointer, dalvik vm will abort
}
可以看出,为了分配内存,虚拟机尽了最大的努力,做了四次尝试。其中进行了两次垃圾收集,第一次不收集 SoftReference ,第二次收集 SoftReference 。从中我们也可以看出垃圾收集的时机,实质上在 dalvik 虚拟机实现中有 3 个时机可以触发垃圾收集的运行:
( 1 )程序员显式的调用 System.gc()
( 2 )内存分配失败时
( 3 )如果分配的对象大小超过 384KB ,运行并发标记 (concurrent mark) ,在下一篇文章中会介绍什么是并发标记
6. 小结
在 dalvik 虚拟机中,内存分配操作的流程相对比较简单直观,从一个堆中分配可用内存,分配失败时触发垃圾收集,接下来的文章中我们仔细分析 dalvik 虚拟机的垃圾收集。