android Dalvik GC

android Dalvik GC

Java Garbage Collection Basics
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

GC的意义
垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存
内存泄露:是指该内存空间使用完毕之后未回收,表现为一个内存对象的生命周期超出了程序需要它的时间长度。

java虚拟机所管理的运行时内存包括以下区域,如图:
这里写图片描述

1、程序计数器:
每一条java线程都有一个独立的程序计数器,我们把线程相互独立隔离的区域叫线程私有的,它是一块较小的空间区域,如果执行的是java方法,
这个计数器记录的是正在执行的虚拟机字节码的指令地址,如果是native的方法,这个计数器的值为空(undefined)。
2、java虚拟机栈:
java虚拟机栈与程序计数器一样,也是一条线程私有的,java虚拟机栈描述的是java方法执行的内存模型,
每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表,操作数栈,动态链路,方法出口等信息。
每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
3、本地方法栈:
本地方法栈和虚拟机栈作用非常相似,不同的是java虚拟机栈是为执行的是java方法服务的,而本地方法栈是为native的方法执行服务的。
4、java堆:
java堆(heap)是java虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在堆上分配内存。
在堆上的内存是分代管理的,分为新生代和老年代,新生代又细分为:Eden,From Survivor,To Survivor,它们空间大小比例为8:1:1。
5、方法区:
方法区与java堆一样,是各个线程共享的内存区域,它用用于存储已被虚拟机加载的类信息,常量,静态变量、即时编译器编译后的代码等数据。
虽然java虚拟机规范把方法区描述为堆得一个逻辑部分,但是它却有一个别名叫Non-Heap(非堆),目的应该是与java堆区分开来,也称“永久代”(Permanent Generation)。
hotspot虚拟机永久代已经完全在JDK 8移除,用Native Memory来的实现,命名为metaSpace
6、运行时常量池:
运行时常量池是方法区的一部分。用于存放编译期生成的各种字面量和符号引用。

Dalvik堆:
Active堆、Zygote堆,Zygote堆是Zygote进程在启动的时候预加载的类、资源和对象(Dalvik的Zygote堆存放的预加载的类都是Android核心类和java运行时库,这部分内容很少被修改,大多数情况父进程和子进程共享这块内存区域),
除此之外的所有对象都是存储在Active堆中(因此GC重点是对Active堆进行操作)。

Dalvik堆分成gygote和Active堆:
主要因为Android是通过fork方法创建到一个新的Zygote进程,为了尽可能的避免父进程和子进程之间的数据拷贝,fork方法使用写时拷贝技术—就是fork的时候不立即拷贝父进程的数据到子进程中,
而是在子进程或者父进程对内存进行写操作时是才对内存内容进行复制,

Dalvik为了对堆进行更好的管理,创建如下对象:
一个Card Table (记录在垃圾收集过程中对象的引用情况)
两个Heap Bitmap: Live Bitmap、Mark Bitmap(描述堆的对象)
一个Mark Stack (标识对象堆栈)

Dalvik创建对象流程

当Dalvik虚拟机的解释器遇到一个new指令时,它就会调用函数Object* dvmAllocObject(ClassObject* clazz, int flags)。
期间完成的动作有(Java堆分配内存前后,要对Java堆进行加锁和解锁,避免多个线程同时对Java堆进行操作。下面所说的堆指的是Active堆):
调用函数dvmHeapSourceAlloc在Java堆上分配指定大小的内存,成功则返回,否则下一步。
执行一次GC, GC执行完毕后,再次调用函数dvmHeapSourceAlloc在Java堆上分配指定大小的内存,成功则返回,否则下一步。
首先将堆的当前大小设置为Dalvik虚拟机启动时指定的Java堆最大值,然后进行内存分配,成功返回失败下一步。这里调用的函数是
dvmHeapSourceAllocAndGrow
调用函数gcForMalloc来执行GC,这里的GC和第二步的GC,区别在于这里回收软引用对象引用的对象,如果还是失败抛出OOM异常。这里调用的函数是dvmHeapSourceAllocAndGrow
Dalvik回收对象流程

Dalvik的垃圾回收策略:
默认是标记、擦除回收算法,即Mark和Sweep两个阶段。

标记与清理的回收算法一个明显的区别就是会产生大量的垃圾碎片,因此程序中应该避免有大量不连续小碎片的时候分配大对象,同时为了解决碎片问题,
Dalvik虚拟机通过使用dlmalloc技术解决。

Mark阶段:
使用了两个Bitmap来描述堆的对象:
一个称为Live Bitmap,用来标记上一次GC时被引用的对象,也就是没有被回收的对象
另一个称为Mark Bitmap,用来标记当前GC有被引用的对象。当Live Bitmap被标记为1,但是在Mark Bitmap中标记为0的对象表明该对象需要被回收

Mark阶段往往要求其它线程处于停止状态,因此Mark又分为两种方式:
串行Mark
并行Mark,并行Mark分两个阶段:
1)、只标记gc_root对象,即在GC开始的瞬间被全局变量、栈变量、寄存器等所引用的对象,该阶段不允许垃圾回收线程之外的线程处于运行状态。
2)、有条件的并行运行其它线程,使用Card Table记录在垃圾收集过程中对象的引用情况。
整个Mark 阶段都是通过Mark Stack来实现递归检查被引用的对象,即在当前GC中存活的对象。标记过程类似用一个栈把第一阶段得到的gc_root放入栈底,然后依次遍历它们所引用的对象(通过出栈入栈),
即用栈数据结构实现了对每个gc_root的递归。

Dalvik的GC类型共有四种:
GC_CONCURRENT: 表示是在已分配内存达到一定量之后触发的GC。
GC_FOR_MALLOC: 表示是在堆上分配对象时内存不足触发的GC。
GC_BEFORE_OOM: 表示是在准备抛OOM异常之前进行的最后努力而触发的GC。
GC_EXPLICIT: 表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。
其中GC_FOR_MALLOC、GC_CONCURRENT和GC_BEFORE_OOM三种类型的GC都是在分配对象的过程触发的。

减少这两种GC事件是,可以通过配置dalvik的系统属性或者修改dalvik的GC算法来实现,
dalvik与GC相关的属性有:
dalvik.vm.heapstartsize:初始化dalvik分配的内存大小。
dalvik.vm.heapgrowthlimit:没有在mainfest中设置Android:largeheap=”true”时,应用的最大内存,超过这个值会有OOM产生。
dalvik.vm.heapsize:在mainfest中设置android:largeheap=”true”时,应用的最大内存,超过这个值会有OOM产生。
dalvik.vm.heaputilization、dalvik.vm.heapminfree 、dalvik.vm.heapmaxfree:dalvik GC时使用的参数。

dalvim GC策略是:
1、根据当前Heap中已分配的内存大小除以dalvik.vm.heaputilization(0.75),得到一个目标值。
2、如果目标值不在(已分配的值+dalvik.vm.heapminfree)到(已分配的值+dalvik.vm.heapmaxfree)这个区间,即取区间边界值做为目标值。
3、虚拟机记录这个目标值作为总的可以分配到的内存大小,同时根据目标值减去固定值(200~500K)当做触发GC_CONCURRENT事件的阈值。
4、为对象分配内存,计算已分配的内存大小;若有达到GC_CONCURRENT的阈值,则产生GC。
5、为对象分配内存,分配失败时,则会产生GC_FOR_ALLOC事件,释放内存;然后再尝试分配。
可以通过调整dalvik.vm.heapminfree 和dalvik.vm.heapmaxfree属性的值,减少GC_FOR_ALLOC和GC_CONCURRENT的次数,
如果这两个值设置的过大,则会导致一次GC的时间过长,从而会看到明显的卡顿现象,设置的值既要使GC的次数减少,也不能是一次GC的时间过长。

在有的平台上,可以通过代码对单个应用的dalvik的属性进行设置,以减少对全局设置对系统的影响,可以在App里面通过如下的方式对当前的App的dalvik属性设置:
import dalvik.system.VMRuntime;
import android.os.SystemProperties;

VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
VMRuntime.getRuntime().setTargetHeapMinFree(2*1024*1024);
VMRuntime.getRuntime().setTargetHeapConcurrentStart(8*1024*1024);

虚拟机将其物理上堆内存划分为:
两个新生代(young generation)
老年代(old generation)
永久代(PermanentGeneration)
这里写图片描述

Stop-the-world
JVM执行GC而停止了应用程序的执行。当Stop-the-world发生时,除了GC所需的线程以外,所有线程都处于等待状态,直到GC任务完成。
GC优化很多时候就是指减少Stop-the-world发生的时间。
MinGC\MajorGC都属于Stop-the-world, 那为什么MajorGC耗时较长呢?因为OldGen包含了大量存货下来的对象。

Young generation 新生代
绝大多数最新被创建的对象会被分配到这里,是用来保存那些第一次被创建的对象,由于大部分对象在创建后会很快变得不可到达,所以很多对象被创建在新生代,然后消失。
对象从Young generation区域消失的过程,称之为Minor GC(Minor GC cleans the Young Generation)
新生代分为三个空间, 空间大小比例8:1:1:
1. Eden空间,内存被调用的起点
2. Survivor 0\Survivor 1 (S0――>S1; age++)
这里写图片描述

这里写图片描述

这里写图片描述

Old generation 老年代
对象没有变得不可达,并且从新生代中存活下来,会被拷贝到这里。其所占用的空间要比新生代多。
也正由于其相对较大的空间,发生在老年代上的GC要比新生代少得多。对象从老年代中消失的过程,称之为Major GC

permanent generation 永久代
也被称为方法区(method area),permanent generation 主要存放.class等文件 、内容:
类的方法、类名称、也包括Interface\abstract class等、常量池、对象数组和类类型数组、JVM内部对象、JIT优化的相关信息、javaSE库类和方法等也会在这里加载.
持久代也在Full GC时回收。

MinorGC:
是清理、整合Young generation的过程,eden的清理,S0\S1的搬移, 年轻代MinorGC发生频率比较高
MinGC
When the new generation fills up, it triggers a minor collection in which the surviving objects are moved to the old generation.
1、As minor GCs continue to occure objects will continue to be promoted to the old generation space.
2、When the eden space fills up, a minor garbage collection is triggered.
MajorGC:
清理、整合Old generation的内存空间
When the old generation fills up, it triggers a major collection which involves the entire object heap.
Full GC
清理整个堆空间,包括年轻代、老年代和永久代,可能导致Full GC:
1、老年代被写满
2、永久代被写满
3、System.gc()被显示调用
4、上一次GC之后Heap的各域分配策略动态变化
许多Major GC是由Minor GC触发的,所以很多情况下将这两种GC分离是不太可能的。另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。

GC机制中的算法:
copying算法(Compacting Collector):
基于coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象面和空闲区域面,在对象面与空闲区域面的切换过程中,程序暂停执行
这里写图片描述

引用计数法(Reference Counting Collector):
堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。任何引用计数器为0的对象实例可以被当作垃圾收集

tracing算法(Tracing Collector) 或 标记-清除算法(mark and sweep):
采用从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,
标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片
根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,
当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点
java中可作为GC Root的对象有:
1、虚拟机栈中引用的对象(本地变量表)
2、方法区中静态属性引用的对象
3、方法区中常量引用的对象
4、本地方法栈中引用的对象(Native对象)

compacting算法 (标记-整理算法)
标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题
1、Marking 标记出unreference object
这里写图片描述
2、Deletion and Compacting
1、释放未被引用的对象
2、紧凑内存,是占用的内存在物理上连续,内存分配器持有空内存的地址头部
这里写图片描述

GC(垃圾收集器)
新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge
Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效。
ParNew收集器(停止-复制算法):新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。
Parallel Scavenge收集器(停止-复制算法):并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%,吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。

老年代收集器使用的收集器:
Serial Old、Parallel Old、CMS
Serial Old收集器(标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本。
Parallel Old收集器(停止-复制算法):Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先
CMS(Concurrent Mark Sweep)收集器(标记-清理算法):高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu追求高响应时间的选择
CMS收集器是基于“标记-清除”算法实现的,整个收集过程大致分为4个步骤:
1、初始标记(CMS initial mark)
2、并发标记(CMS concurrenr mark)
3、重新标记(CMS remark)
4、并发清除(CMS concurrent sweep)

垃圾回收具体都是通过调用函数void dvmCollectGarbageInternal(const GcSpec* spec) 来执行垃圾回收。
根据垃圾回收线程和工作线程的关系分为:
并行GC – 在回收阶段有选择性的停止当前工作线程,并行GC需要多执行一次标记根集对象以及递归标记那些在GC过程被访问了的对象的操作,意味着并行GC需要花费更多的CPU资源
非并行GC – 在垃圾回收阶段停止所有工作线程。

//GC类型结构体定义
struct GcSpec {
/* If true, only the application heap is threatened. */
bool isPartial;
/* If true, the trace is run concurrently with the mutator. */
bool isConcurrent;
/* Toggles for the soft reference clearing policy. */
bool doPreserve;
/* A name for this garbage collection mode. */
const char *reason;
};
dvmCollectGarbageInternal函数的内部逻辑如下
这里写图片描述

并行和串行gc的区别在于:
并行gc会在mark第二阶段将非gc线程唤醒;当mark的第二阶段完成之后,再次停止非gc线程;利用cardtable的信息再次进行一个mark操作,此时的mark操作比第一个mark操作要快得多。
并行gc会在sweep阶段将非gc线程唤醒。
串行gc会在垃圾回收开始就暂停所有非gc线程,知道垃圾回收结束。
并行gc涉及到两次的mark操作,消耗cpu时间

减少GC开销的措施
1、不要显式调用System.gc()
  此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。
2、尽量减少临时对象的使用
  临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,减少了主GC的机会。
3、对象不用时最好显式置为Null
  一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
4、尽量使用StringBuffer,而不用String来累加字符串
  由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象, StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
5、能用基本类型如Int,Long,就不用Integer,Long对象
  基本类型变量占用的内存资源比相应对象占用的少得多。
6、尽量少用静态对象变量
  静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
7、分散对象创建或删除的时间
  集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。
集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值