android内存管理
来源:互联网 发布:druid优化 编辑:程序博客网 时间:2024/04/29 19:58
- Linux内存管理
- 基本逻辑:
module_init(lowmem_init); --> register_shrinker(&lowmem_shrinker);
lowmem_shrink函数\
首先找到满足条件的进程
获取当前系统的空闲内存,根据空闲内存数目,确定min_score_adj。不是内核线程进程和它的所有子进程都不拥有task_lock进程的oom_score_adj大于min_score_adj进程的oom_score_adj大于min_score_adj,并且和min_score_adj最相近具有相同oom_score_adj,但是占用内存数量最大的进程
- 参数及设置
cat /sys/module/lowmemorykiller/parameters/adj
0,58,117,235,529,1000
root@android:/ # cat /sys/module/lowmemorykiller/parameters/minfree
cat /sys/module/lowmemorykiller/parameters/minfree
8192,10240,12288,14336,16384,20480
cat oom_adj
8
root@android:/proc/10029 # cat oom_score
cat oom_score
487
root@android:/proc/10029 # cat oom_score_adj
cat oom_score_adj
470
15 write /proc/1/oom_score_adj -1000
...
342 write /proc/sys/vm/overcommit_memory 1343 write /proc/sys/vm/min_free_order_shift 4344 chown root system /sys/module/lowmemorykiller/parameters/adj345 chmod 0220 /sys/module/lowmemorykiller/parameters/adj346 chown root system /sys/module/lowmemorykiller/parameters/minfree347 chmod 0220 /sys/module/lowmemorykiller/parameters/minfree
ProcessList中定义了不同类型APP的adj值。
http://androidxref.com/5.0.0_r2/xref/frameworks/base/services/core/java/com/android/server/am/ProcessList.java#72
android进程状态改变时调用 updateOomAdjLocked() 其中计算调整进程的oom_score_adj的值。
http://androidxref.com/5.0.0_r2/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#17626
- Java虚拟机内存管理
- 算法
大部分的android程序有Java构建,由Java虚拟机执行。(参见<认识理解zygote>)。在zygote启动时创建了java vm,设定了堆和栈空间的最大值。
托管程序对于内存的使用有Java虚拟机进行管理。
android内存管理原理 --- 介绍了多种GC算法http://www.cnblogs.com/killmyday/archive/2013/06/12/3132518.html
Android系统采用的是标注并删除和拷贝GC算法
JVM采用逐代回收算法(一种优化的GC算法)
- android 实现
在Android中 ,实现了标注与清理(Mark and Sweep)和拷贝GC,但是具体使用什么算法是在编译期决定的,无法在运行的时候动态更换 – 至少在目前的版本上(4.2)还是这样。在Android的dalvik虚拟机源码的Android.mk文件(路径是/dalvik/vm/Dvm.mk)里,有类似代码清单14 - 5的代码,即如果在编译dalvik虚拟机的命令中指明了"WITH_COPYING_GC"选项,则编译"/dalvik/vm/alloc/Copying.cpp"源码 – 此是Android中拷贝GC算法的实现,否则编译"/dalvik/vm/alloc/HeapSource.cpp" – 其实现了标注与清理GC算法,也就是本节分析的重点。
代码清单14 - 5 编译器指定使用拷贝GC还是标注与清理GC算法
WITH_COPYING_GC := $(strip $(WITH_COPYING_GC))
ifeq ($(WITH_COPYING_GC),true)
LOCAL_CFLAGS += -DWITH_COPYING_GC
LOCAL_SRC_FILES += \
alloc/Copying.cpp.arm
else
LOCAL_SRC_FILES += \
alloc/DlMalloc.cpp \
alloc/HeapSource.cpp \
alloc/MarkSweep.cpp.arm
endif
注意本节中分析的Android源码,可以在网址:http://androidxref.com/source/xref/ 中在线浏览。
- 在Java中,对象是分配在Java内存堆之上的,当Java程序启动后,JVM会向操作系统申请保留一大块连续的内存。
在Android源码中,这个过程分为下面几步:
- dvmStartup函数(/dalvik/vm/Init.cpp:1376)解析完传入虚拟机的命令行参数,调用dvmGcStartup函数初始化GC组件。
- dvmGcStartup函数(/dalvik/vm/alloc/Alloc.cpp:30)负责初始化几个GC线程同步原语,再调用dvmHeapStartup函数初始化GC内存堆(即Java内存堆)。
- dvmHeapStartup函数(/dalvik/vm/alloc/Heap.cpp:75)则根据GC参数设置调用dvmHeapSourceStartup函数向操作系统申请一大块连续的内存空间,这个内存空间会自动增长,在默认设置中(/dalvik/vm/Init.cpp:1237),该内存堆的初始大小是2MB – 由gDvm.heapStartingSize指定,内存堆最大不超过16MB(Java程序用完这16MB内存就会导致OOM异常) – 由gDvm.heapGrowthLimit指定,如果gDvm.heapGrowthLimit的值为0的话(即表示可以无限增长),则将最大值限定为gDvm.heapMaximumSize的值。申请完内存空间之后,初始化一个名为clearedReferences的队列(/dalvik/vm/alloc/Heap.cpp:98),这个队列将用在保存finalizable对象,以在另一个线程中执行它们的finalize函数。最后,dvmHeapStartup函数还要初始化数据结构Card Table(/dalvik/vm/alloc/Heap.cpp:100),如代码清单14 - 6。
代码清单14 - 6 dvmHeapStartup初始化GC内存堆
75 bool dvmHeapStartup()
76 {
77 GcHeap *gcHeap;
78
79 if (gDvm.heapGrowthLimit == 0) {
80 gDvm.heapGrowthLimit = gDvm.heapMaximumSize;
81 }
82
83 gcHeap = dvmHeapSourceStartup(gDvm.heapStartingSize,
84 gDvm.heapMaximumSize,
85 gDvm.heapGrowthLimit);
86 if (gcHeap == NULL) {
87 return false;
88 }
89 gcHeap->ddmHpifWhen = 0;
90 gcHeap->ddmHpsgWhen = 0;
91 gcHeap->ddmHpsgWhat = 0;
92 gcHeap->ddmNhsgWhen = 0;
93 gcHeap->ddmNhsgWhat = 0;
94 gDvm.gcHeap = gcHeap;
95
96 /* Set up the lists we'll use for cleared reference objects.
97 */
98 gcHeap->clearedReferences = NULL;
99
100 if (!dvmCardTableStartup(gDvm.heapMaximumSize, gDvm.heapGrowthLimit)) {
101 LOGE_HEAP("card table startup failed.");
102 return false;
103 }
104
105 return true;
106 }
- dvmHeapSourceStartup函数(/dalvik/vm/alloc/HeapSource.cpp:541)通过dvmAllocRegion函数向操作系统申请保留一大块连续的内存地址空间,其大小是内存堆最大可能的大小(/dalvik/vm/alloc/HeapSource.cpp:563),成功后,再根据内存堆的初始大小申请内存。如默认情况下,Java内存堆的初始大小是2MB,而最大能增长到16MB,那么一开始dvmHeapSourceStartup会申请16MB大小的地址空间,但一开始只分配2MB的内存备用。在底层内存实现上,Android系统使用的是dlmalloc实现-又叫msspace,这是一个轻量级的malloc实现。
除了创建和初始化用于存储普通Java对象的内存堆,Android还创建三个额外的内存堆:用来存放堆上内存被占用情况的位图索引"livebits"、在GC时用于标注存活对象的位图索引"markbits",和用来在GC中遍历存活对象引用的标注栈(Mark Stack)。
dvmHeapSourceStartup函数运行完成后,HeapSource、Heap、livebits、markbits以及mark stack等数据结构的关系如图 14 - 15所示。
图 14 - 15 GC堆上HeapSource、Heap等数据结构的关系
其中虚拟机通过一个名为gHs的全局HeapSource变量来操控GC内存堆,而HeapSource里通过heaps数组可以管理多个堆(Heap),以满足动态调整GC内存堆大小的要求。另外HeapSource里还维护一个名为"livebits"的位图索引,以跟踪各个堆(Heap)的内存使用情况。剩下两个数据结构"markstack"和"markbits"都是用在垃圾回收阶段,后面会讲解。
- 而dvmAllocRegion函数(/dalvik/vm/Misc.cpp:612)则通过ashmem和mmap两个系统调用分配内存地址空间,其中ashmem是Android系统对Linux的一个扩展,而mmap则是Linux系统提供的系统调用,请读者自行搜索参阅相关文档了解其用法。
- 这些步骤做完之后,一个Android应用的内存情况如图 14 - 16所示,虚线是应用实际申请的地址空间范围,而实线部分则是已经分配的内存:
图 14 - 16 GC向操作系统申请地址空间和内存
- 当需要应用需要分配内存,即通过"new"关键字创建一个实例时,在Android源码的过程大致如下:
- 首先虚拟机在执行Java class文件时,遇到"new "或" newarray"指令(所有的Java字节指令码请参考维基百科:http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings),表示要创建一个对象或者数组的实例,这里为了简单起见,我们只看新建一个对象实例的情形。
- 虚拟机的JIT编译器执行"new"指令,针对不同的CPU架构,"new"指令都有相应的机器码与其对应,如ARM架构,JIT执行/dalvik/vm/mterp/armv5te/OP_NEW_INSTANCE.S中的机器码;而x86架构,则是/dalvik/vm/mterp/x86/OP_NEW_INSTANCE.S中的机器码。"OP_NEW_INSTANCE"函数的工作就是加载"new"指令的对象类型参数,获取对象需要占用的内存大小信息,然后调用"dvmAllocObject"分配必要的内存(/dalvik/vm/mterp/armv5te/OP_NEW_INSTANCE.S:29),当然还会处理必要的异常。
- dvmAllocObject函数(/dalvik/vm/alloc/Alloc.cpp:181)调用dvmMalloc根据对象大小分配内存空间,成功后,调用对象的构造函数初始化实例(/dalvik/vm/alloc/Alloc.cpp:191)。
- 程序在运行的过程中不停的创建新的对象并消耗内存,直到GC内存用光,这时再要创建新对象时,就会触发GC线程启动垃圾回收过程,在Android源码中:
- dvmMalloc函数(/dalvik/vm/alloc/Heap.cpp:333)直接将分配内存的工作委托给函数tryMalloc。
- tryMalloc函数(/dalvik/vm/alloc/Heap.cpp:178)首先尝试用dvmHeapSourceAlloc函数分配内存,如果失败的话,唤醒或创建GC线程执行垃圾回收过程,并等待其完成后重试dvmHeapSourceAlloc(/dalvik/vm/alloc/Heap.cpp:201);如果dvmHeapSourceAlloc再次失败,说明当前GC堆中大部分对象都是存活的,那么调用dvmHeapSourceAllocAndGrow(/dalvik/vm/alloc/Heap.cpp:222)尝试扩大GC内存堆 – 前面说过,一开始GC堆会根据初始大小向操作系统申请保留一块内存,如果这块内存用完了,GC堆就会再次向操作系统申请一块内存,直到用完限额。
- dvmMalloc函数根据内存分配是否成功来执行相应的操作,如内存分配失败时,抛出OOM(Out Of Memory)异常(/dalvik/vm/alloc/Heap.cpp:383)。
- Android源码中垃圾回收过程大致如下:
- dvmCollectGarbageInternal函数(/dalvik/vm/alloc/Heap.cpp:440)开始垃圾回收过程,其首先调用dvmSuspendAllThreads(/dalvik/vm/Thread.cpp:2539)暂停系统中除与调试器沟通的其他所有线程(/dalvik/vm/alloc/Heap.cpp:462);
- 如果没有启用并行GC的话,虚拟机会提高GC线程的优先级,以防止GC线程被其它线程占用CPU。
- 接下来调用dvmHeapMarkRootSet函数(/dalvik/vm/alloc/Heap.cpp:488)来遍历所有可从GC Root访问到的对象列表,dvmHeapMarkRootSet函数(/dalvik/vm/alloc/MarkSweep.cpp:181)的注释中也列出了GC Root列表。其调用dvmVisitRoot遍历GC Roots,代码清单14 - 1是dvmVisitRoot的源码(/dalvik/vm/alloc/Visit.cpp:212),笔者在其中以注释的方式批注关键代码。完整的GC Root列表有兴趣的读者可以参阅链接:http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html。
代码清单14 - 7 在虚拟机中通过dvmVisitRoot遍历GC Roots
//
// visitor是一个回调函数,dvmHeapMarkRootSet传进来的是rootMarkObjectVisitors
// (位于/dalvik/vm/alloc/MarkSweep.cpp:145),这个回调函数的作用就是标注(Mark)
// 所有的GC root,并将它们的指针压入标注栈(Mark Stack)中。
//
// 第二个参数arg实际上是GcMarkContext对象,用于找到GC Roots后,回传给回调函数visitor
// 的参数。
//
void dvmVisitRoots(RootVisitor *visitor, void *arg)
{
assert(visitor != NULL);
// 所有已加载的类型都是GC Roots,这也意味着类型中所有的静态变量都是GC Roots
visitHashTable(visitor, gDvm.loadedClasses, ROOT_STICKY_CLASS, arg);
// 基本类型也是GC Roots,包括
// void, boolean, byte, short, char, int, long, float, double
visitPrimitiveTypes(visitor, arg);
// 调试器对象注册表中的对象(debugger object registry),这些对象
// 基本上是调试器创建的,因此不能把它们当作垃圾回收了,否则调试器
// 就无法正常工作了。
if (gDvm.dbgRegistry != NULL) {
visitHashTable(visitor, gDvm.dbgRegistry, ROOT_DEBUGGER, arg);
}
// 所有interned的字符串,interned string是虚拟机中保证的只有唯一一份拷贝的字符串
if (gDvm.literalStrings != NULL) {
visitHashTable(visitor, gDvm.literalStrings, ROOT_INTERNED_STRING, arg);
}
// 所有的JNI全局引用对象(JNI global references),JNI全局引用对象是
// JNI代码中,通过NewGlobalRef函数创建的对象
dvmLockMutex(&gDvm.jniGlobalRefLock);
visitIndirectRefTable(visitor, &gDvm.jniGlobalRefTable,, ROOT_JNI_GLOBAL, arg);
dvmUnlockMutex(&gDvm.jniGlobalRefLock);
// 所有的JNI局部引用对象(JNI local references)
// 关于JNI局部和全部变量的使用,可以参考下面的网页链接:
// http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/refs.html
dvmLockMutex(&gDvm.jniPinRefLock);
visitReferenceTable(visitor, &gDvm.jniPinRefTable,, ROOT_VM_INTERNAL, arg);
dvmUnlockMutex(&gDvm.jniPinRefLock);
// 所有线程堆栈上的局部变量和其它对象,如线程本地存储里的对象等等
visitThreads(visitor, arg);
// 特殊的异常对象,如OOM异常对象需要在内存不够的时候创建,为了防止内存不够而无法创建
// OOM对象,因此虚拟机会在启动时事先创建这些对象。
(*visitor)(&gDvm.outOfMemoryObj,, ROOT_VM_INTERNAL, arg);
(*visitor)(&gDvm.internalErrorObj,, ROOT_VM_INTERNAL, arg);
(*visitor)(&gDvm.noClassDefFoundErrorObj,, ROOT_VM_INTERNAL, arg);
}
dvmHeapMarkRootSet是执行标注过程的主要代码,在前文说过,通常的实现会在对象实例前面放置一个对象头,里面会存放是否标注过的标志,而在Android系统里,采取的是分离式策略,而是将标注用的标志位放到HeapSource里的"markbits"这个位图索引结构,笔者猜测这么做的目的是为了节省内存。图 14 - 17是dvmHeapMarkRootSet函数快要标注完存活对象时(正在标注最后一个对象H),GC内存堆的数据结构。
图 14 - 17 GC执行完标注过程后的HeapSource结构
其中"livebits"位图索引还是维护堆上已用的内存信息;而"markbits"这个位图索引则指向存活的对象,在图 14 - 17中, A、C、F、G、H对象需要保留,因此"markbits"分别指向他们(最后的H对象尚在标注过程中,因此没有指针指向它);而"markstack"就是在标注过程中跟踪当前需要处理的对象要用到的标志栈了,此时其保存了正在处理的对象F、G和H。
- 在标注(Mark)过程中,调用dvmHeapScanMarkedObjects和dvmHeapProcessReferences函数(/dalvik/vm/alloc/MarkSweep.cpp:776)将实现了finalizer的对象添加到finalizer对象队列中,以便在下次GC中执行这些对象的finalize函数。
- 标识出所有的垃圾内存之后,调用dvmHeapSweepSystemWeaks和dvmHeapSweepUnmarkedObjects(/dalvik/vm/alloc/MarkSweep.cpp:902)等函数清理内存,但并不压缩内存,这是因为Android的GC是基于dlmalloc之上实现的,GC将所有的内存分配和释放的操作都转交给dlmalloc来处理。在这个过程中, Android系统不做压缩内存处理,据说是为了节省执行的CPU指令,从而达到延长电池寿命的目的,因此dvmCollectGarbageInternal做了一个小技巧,调用dvmHeapSourceSwapBitmaps函数(/dalvik/vm/alloc/Heap.cpp:575)将"livebits"和"markbits"的指针互换,这样就不需要在清理完垃圾对象后再次维护"livebits"位图索引了,如图 14 - 18所示:
图 14 - 18 GC清理完内存后堆上的数据结构
- 做完上面的操作之后,GC线程再通过dvmResumeAllThreads函数唤醒所有的线程(/dalvik/vm/alloc/Heap.cpp:624)。
- 虽然GC可以自动回收不再使用的内存,但有很多资源是虚拟机也无法管理的,如进程打开的数据库连接、网络端口以及文件等。针对这些资源,GC线程可以在垃圾回收过程中,标示出其是垃圾,需要释放,但是却不清楚如何释放它们,因此Java对象提供了一个名为finalize的函数,以便对象实现自定义的清除资源的逻辑。
如代码清单14 - 1是一个实现finalize函数的对象,在Java中,finalize对象定义在System.Object类中,即意味着所有对象都有这个函数,当子类重载了这个函数,即向虚拟机表明自己需要与其他类型区别对待。
代码清单14 - 8 实现finalize函数的简单对象
1 class DemoClass {
2 public int X;
3
4 public void testMethod() {
5 System.out.println("X: " + new Integer(X).toString());
6 }
7
8 @Override
9 protected void finalize () throws Throwable {
10 System.out.println("finalize函数被调用了!");
11 // 实现自定义的资源清除逻辑!
12 super.finalize();
13 }
14 }
一些有C++编程经验的读者可能很容易将finalize函数与析构函数对应起来,但是两者是完全不同的东西,在C++中,调用了析构函数之后,对象就被释放了,然而在Java中,如果一个类型实现了finalize函数,其会带来一些不利影响,首先对象的存活周期会更长,至少需要两次垃圾回收才能销毁对象;第二对象同时会延长其所引用到的对象存活周期。如代码清单14 - 2中(示例代码javagc-simple)在第3行创建并使用了DemoClass以在内存中生成一些垃圾,并执行三次GC。
代码清单14 - 9 实现finalize函数的简单对象
1 public class gcdemo {
2 public static void main(String[] args) throws Exception {
3 generateGarbage();
4 System.gc();
5 Thread.sleep(1000);
6
7 System.gc();
8 Thread.sleep(1000);
9
10 System.gc();
11 Thread.sleep(1000);
12 }
13
14 public static void generateGarbage() {
15 DemoClass g = new DemoClass();
16 g.X =123;
17 g.testMethod();
18 }
19 }
连接好设备,打开logcat日志,并执行示例代码根目录中的run.sh,得到的输出类似图 14 - 8,每一行输出对应代码清单14 - 2中的一次System.gc调用,可以看到第一次GC过程中释放了223个对象,如果运行示例程序javagc,会发现第一次GC之后,DemoClass的finalize函数就会被调用 – 为了避免System.out.println中的字符串对象影响GC的输出,图 14 - 8是javagc-simple的输出结果。第二次GC过程中又释放了34个对象,其中就有DemoClass的实例,以及其所引用到的其它对象。这时所有垃圾对象都被回收了,因此在执行第三次GC过程时,没有回收到任何内存。
图 14 - 19 程序中使用了实现finalize函数对象之后实施三次GC的结果
前文讲到Android源码中通过dvmHeapScanMarkedObjects函数在GC堆上扫描垃圾对象,并将finalizable对象添加到finalize队列中,其具体过程如下:
- dvmHeapScanMarkedObjects函数(/dalvik/vm/alloc/MarkSweep.cpp:595)将所有识别出来的可以被GC Root引用的对象放到名为"mark stack"的堆栈中,再调用processMarkStack函数处理需要特殊处理的对象。
- processMarkStack函数(/dalvik/vm/alloc/MarkSweep.cpp:471)调用scanObject函数处理"mark stack"中的每个对象。
- scanObject函数(/dalvik/vm/alloc/MarkSweep.cpp:454)首先判断对象是保存Java类型信息的类型对象,还是数组对象,还是普通的Java对象,针对这三种对象进行不同的处理。由于finalize对象是普通的Java对象,因此这里我们只看相应的scanDataObject函数。
- scanDataObject函数(/dalvik/vm/alloc/MarkSweep.cpp:438)先扫描对象的各个成员,并标记其所有引用到的对象,最后调用delayReferenceReferent函数根据对象的类型,将其放入相应的待释放队列中,如对象是fianlizeable对象的话,则放入finalizerReferences队列中(/dalvik/vm/alloc/MarkSweep.cpp:426);如对象是WeakReference对象的话,则将其放入weakReferences队列中(/dalvik/vm/alloc/MarkSweep.cpp:424)。
- dvmHeapProcessReferences函数(/dalvik/vm/alloc/MarkSweep.cpp#776)在垃圾对象收集完毕后,负责将finalize队列从虚拟机的native端传递到Java端。其调用enqueueFinalizerReferences函数通过JNI方式将finalize对象的引用传递到Java端的一个java.lang.ref.ReferenceQueue当中,详细的调用方式请参见enqueueFinalizerReferences函数(/dalvik/vm/alloc/MarkSweep.cpp:729)和enqueueReference函数(/dalvik/vm/alloc/MarkSweep.cpp:653)。
- 而在JVM虚拟机启动时,dvmStartup函数(/dalvik/vm/Init.cpp:1557)会在准备好Java程序运行所需的所有环境之后,调用dvmGcStartupClasses函数(/dalvik/vm/alloca/Alloc.cpp:71)启动几个与GC相关的后台Java线程 ,这些线程在java.lang.Daemons中定义(/libcore/luni/src/main/java/java/lang/Daemons.java),其中一个线程就是执行java对象finalize函数的HeapWorker线程,之所以要将收集到的java finalize对象引用从虚拟机(native)一端传递到Java端,是因为finalize函数是由java语言编写的,函数里可能会用到很多java对象。这也是为什么如果对象实现了finalize函数,不仅会使其生命周期至少延长一个GC过程,而且也会延长其所引用到的对象的生命周期,从而给内存造成了不必要的压力。
- tips
- android 内存溢出 内存管理
- android内存管理
- Android 的内存管理
- Android 之 内存管理
- Android 之 内存管理
- Android 之 内存管理
- android内存管理等
- Android 之 内存管理
- Android 之 内存管理
- Android中的内存管理
- Android 之 内存管理
- Android 之 内存管理
- Android内存管理分析
- android+内存管理
- android基础--内存管理
- android 内存管理
- Android内存管理分析
- Android内存管理
- Custom Class Loading in Dalvik
- Python 遍历文件夹中的指定类型文件
- linux下ssh安装 和 scp命令 使用
- 多线程资源共享
- 黑马程序员-----@class的使用
- android内存管理
- 正则表达式
- #13 LeetCode——Roman to Integer
- UVA 10570 Meeting with Aliens
- Django: 'module' object has no attribute 'index'
- mysql分库分表
- 比较实用的ADT快捷键
- 51nod 算法马拉松6(索函数)(规律题目)
- 【内存优化】SparseArray源码分析