本文整理自网络,以及自己的一个简单例子

虚拟机回收机制

 

1.       引用计数法(Reference Counting Collector) 

引用计数法是唯一没有使用根集的垃圾回收的法,该算法使用引用计数器来区分存活对象和不再使用的对象。一般来说,堆中的每个对象对应一个引用计数器。当每一次创建一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了作用域后(该对象丢弃不再使用),引用计数器减1,一旦引用计数器为0,对象就满足了垃圾收集的条件。

 

基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,适宜地必须实时运行的程序。但引用计数器增加了程序执行的开销,因为每次对象赋给新的变量,计数器加1,而每次现有对象出了作用域生,计数器减1。

 

ps:用根集的方法(既有向图的方法)进行内存对象管理,可以消除循环引用的问题.就是说如果有三个对象相互引用,只要他们和根集是不可达的,gc也是可以回收他们.根集的方法精度很高,但是效率低.计数器法精度低(无法处理循环引用),但是执行效率高. 

 

2.       tracing算法(TracingCollector) 

tracing算法是为了解决引用计数法的问题而提出,它使用了根集的概念。基于tracing算法的垃圾收集器从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象,例如对每个可达对象设置一个或多个位。在扫描识别过程中,基于tracing算法的垃圾收集也称为标记和清除 (mark-and-sweep)垃圾收集器。

3.       compacting算法(CompactingCollector) 

为了解决堆碎片问题,基于tracing的垃圾回收吸收了Compacting算法的思想,在清除的过程中,算法将所有的对象移到堆的一端,堆的另一端就变成了一个相邻的空闲内存区,收集器会对它移动的所有对象的所有引用进行更新,使得这些引用在新的位置能识别原来的对象。在基于Compacting 算法的收集器的实现中,一般增加句柄和句柄表。 

4.       copying算法(CopingCollector) 

该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。

 

将内存分为两个区域(fromspace和to space)。所有的对象分配内存都分配到from space。在清理非活动对象阶段,把所有标志为活动的对象,copy到to space,之后清楚from space空间。然后互换from sapce和to space的身份。既原先的from space变成to sapce,原先的to space变成from space。每次清理,重复上述过程。

 

优点:copy算法不理会非活动对象,copy数量仅仅取决为活动对象的数量。并且在copy的同时,整理了heap空间,即,to space的空间使用始终是连续的,内存使用效率得到提高。 

 

缺点:划分fromspace和to space,内存的使用率是1/2。收集器必须复制所有的活动对象,这增加了程序等待时间。

5、generation算法(GenerationalCollector) 

 

来自IBM的一组统计数据:98%的java对象,在创建之后不久就变成了非活动对象;只有2%的对象,会在长时间一直处于活动状态。 

 

(1) young generation

年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在 Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到tenured generation。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。

 young generation的gc称为minor gc。经过数次minor gc,依旧存活的对象,将被移出young generation,移到tenured generation 

(2) tenured generation 

生命周期较长的对象,归入到tenured generation。一般是经过多次minor gc,还 依旧存活的对象,将移入到tenured generation。(当然,在minor gc中如果存活的对象的超过survivor的容量,放不下的对象会直接移入到tenured generation)

tenured generation的gc称为major gc,就是通常说的full gc。 

采用compactiion算法。由于tenured generaion区域比较大,而且通常对象生命周期都比较长,compaction需要一定时间。所以这部分的gc时间比较长。 

minor gc可能引发full gc。当eden+from space的空间大于tenured generation区的剩余空间时,会引发full gc。这是悲观算法,要确保eden+from space的对象如果都存活,必须有足够的tenured generation空间存放这些对象。

(3) permanent generation 

该区域比较稳定,主要用于存放classloader信息,比如类信息和method信息。 

对于spring hibernate这些需要动态类型支持的框架,这个区域需要足够的空间。(这部分空间应该存在于方法区而不是heap中)。 

 

关于shallow size、retained size

Shallow size就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。在32位系统上,对象头占用8字节,int占用4字节,不管成员变量(对象或数组)是否引用了其他对象(实例)或者赋值为null它始终占用4字节。故此,对于String对象实例来说,它有三个int成员(3*4=12字节)、一个char[]成员(1*4=4字节)以及一个对象头(8字节),总共3*4 +1*4+8=24字节。根据这一原则,对String a=”rosen jiang”来说,实例a的shallow size也是24字节(很多人对此有争议,请看官甄别并留言给我)。

 

Retained size是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。为了更好的理解retained size,不妨看个例子。

 

把内存中的对象看成下图中的节点,并且对象和对象之间互相引用。这里有一个特殊的节点GC Roots,正解!这就是reference chain的起点。



从obj1入手,上图中蓝色节点代表仅仅只有通过obj1才能直接或间接访问的对象。因为可以通过GC Roots访问,所以左图的obj3不是蓝色节点;而在右图却是蓝色,因为它已经被包含在retained集合内。

所以对于左图,obj1的retained size是obj1、obj2、obj4的shallow size总和;右图的retained size是obj1、obj2、obj3、obj4的shallow size总和。obj2的retained size可以通过相同的方式计算。

 

MAT是一个用来分析你的程序heap dump的工具。这个工具很强大,我们今天不全部介绍。

当转换完HPROF文件后,你会看见一个饼状图的概览,它向你展示了那些大的对象。

 

List objects>with outgoing references:查看这个对象持有的外部对象引用

List object>with incoming references:查看这个对象被哪些外部对象引用。

Show object by class>with outgoingreferences:查看这个对象类型持有的外部对象引用

Show object by class>with incomingreferences:查看这个对象类型被哪些外部对象引用

Paths to gc root:显示不同的到根节点的路径

 

例子

1.       新建一个Android工程,在MainActivity的oncreate中添加

List<String>list = new ArrayList<String>();    

while(1<2){      

list.add("OutOfMemoryErrorsoon");    

}

 

2.       在程序运行过程中,打开ddms,点击dump heap file,

 

可以看到一个heap的概况

Histogram:类列表以及实例数量

Dominator Tree:列出所有对象在整个内存对象中得比例

Top Consumers:根据类名和包名列出开销大的对象。

3.       点击Leak Supspects,可以看到MAT帮我们分析的内存泄露的情况

4.       点击details,可以看见更详细的信息

 

 

经验:

首先可以直接查看MAT分析内存泄露的问题,或者查看retained size大得数据,可以用Top consumers和donimator tree来查看,最后通过list objects>with outgoing references来查看具体持有哪些对象来分析泄露原因。

参考

http://hi.baidu.com/lennyxue/item/bb2633eae2ebd2c2bbf37dae

http://www.blogjava.net/rosen/archive/2010/05/21/321575.html

http://www.vogella.com/tutorials/EclipseMemoryAnalyzer/article.html

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐