您的位置:首页 > 编程语言 > Java开发

JAVA垃圾回收机制

2016-10-21 21:10 323 查看

一、垃圾回收机制中的算法

任何一种垃圾回收算法一般要做两种事情

1)发现无用信息对象;

2)回收被无用对象占用的空间,使得该空间可再次被使用

引用计数法(reference counting collector)

1.1算法分析

引用技术算法是垃圾回收器早期策略。在这个方法中,堆中的每一个实例都有一个引用计数器,一个对象被创建时,且该对象实例分配给一个变量时,该变量计数器置为1,当任何其他变量赋值为这个对象时,计数加1;但当一个对象实例的某个引用超过了声明周期或者被设置为一个新值时,对象实例的引用计数器减1,任何引用计数器为0的对象实例可以当作垃圾回收。当一个对象的实例被垃圾收集时,它引用的任何对象实例的引用计数器减1.

1.2优缺点

优点:

引用技术收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利

缺点

无法检测出循环引用,例如扶对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

例子:

public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();

object1.object = object2;
object2.object = object1;

object1 = null;
object2 = null;
}
}


因为他们互相引用对方,导致他们的引用计数器都不为0,那么垃圾收集器就永远不会回收。

2、tracing算法(tracing collector)或:标记-清除算法(mark and sweep)

2.1根搜索算法

tracing算法是为了解决引用计数法的问题而提出,它使用了根集的概念。基于tracing算法的垃圾收集器从根开始扫描,识别出那些对象可达和不可达,并用某种方式标记可达对象。



tracing搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点引用的节点,当所用的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用节点。

java中可作为GC Root的对象有

虚拟机栈中引用的对象

方法区中静态属性引用的对象

方法区中常量引用的对象

本地方法栈中引用的对象

2.2tracing算法的示意图



2.3分析

从根集合进行扫描,对存货的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收。不需要进行对象的移动,并且仅对不存货的对象进行处理,在存货对象比较多的情况下极为高效,但这样会容易造成内存碎片

3、compacting算法(compacting collector)标记-整理算法



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

4、copying算法(copying collector)



该算法的剔除是为了客服句柄的开销和解决堆岁盘的垃圾回收。 它开始时把堆分成一个对象面和多个空闲面,程序从对象面为对象分配空间,当对象面满了,基于copying算法的垃圾回收器就从根集合中扫描活动对象,并将每个活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。

一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象区和空闲区域区,在对象区与空闲区域的切换过程中,程序暂停执行。

5、generation算法(Generation Collector)

stop-and-copy垃圾收集器的一个缺陷是收集器必须复制所有的活动对象,这增加了程序等待时间,这是coping算法低效的原因。因此推出了分代的垃圾回收策略



分代回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以提高回收效率。

年轻代(young generation)

所有新生成的对象首先都是放在年轻代的。年轻待的目标就是尽可能快速的收集那些生命周期短的对象。

新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0/survivor1)区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。回收时先将Eden区存活对象复制到survivor0区,然后清空Eden区,当这个survivor0区也放满了时,则将Eden和Survivor0存活的对象复制到Survivor1区,然后清空Eden和这个Survivor0.此时Survivor0是空的,然后将Survivor1和Survivor0调换。即是保持Survivor1为空,如此往复。

当Survivor1也不足以存放Eden和Survivor0的存活对象时,就将存货对象直接存放到老年代。若是老年代也满了,就会触发一次Full GC,也就是新生代。老年代都进行回收

新生代发生的GC也叫做Minor GC,MinorGC发生的频率比较高(不一定要eden满了才触发)

年老代(old generation)

在年轻代中经历了N次垃圾回收后仍然存货的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

内存比新生代也大很多(大概是2:1),当年老代内存满了的时候触发Major GC 即是Full GC,full gc发生频率比较低,老年代对象存货时间比较成,存活率比较高。

持久代(permannent generation)

用于存放静态文件,如java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如hibernate等,在这种时候需要设置一个比较大的持久空间来存放这些运行过程中新增的类。

二、GC(垃圾收集器)

新生代使用的收集器:serial、parNew、parallel scavenge

老年代使用的收集器:Serial old、Parallel old、CMS



Serial收集器(复制算法)

新生代单线程收集器,标记和清理都是单线程,优点是简单高效

Serial Old收集器(标记-整理算法)

老年代的单线程收集器,Serial收集器的的老年代版本

ParNew收集器(停止-复制算法)

可以认为是Serial收集器的多线程版本,在多核CPU环境下有着跟好表现

Parallel Scavenge(停止-复制算法)

并行收集器,追求搞吞吐量,搞笑利用CPU,吞吐量一般在90%,吞吐量=用户相乘时间/(用户线程时间+GC线程时间),适合后台应用等对交互相应要求不搞的场景

Parallel OLd()

Parallel Scavenge收集器的老年代版本

CMS(Concurrent mark sweep)收集器(标记-清理)

高并发。低停顿,追求最短GC回收停顿时间,CPU占用比较高,响应时间块,停顿时间短,多核CPU追求高响应时间的选择

三、GC的执行机制

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。

Scavenge GC

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

Full GC

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

1.年老代(Tenured)被写满

2.持久代(Perm)被写满

3.System.gc()被显示调用

4.上一次GC之后Heap的各域分配策略动态变化

四、java也会出现内存泄漏问题

1.静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。

Static Vector v = new Vector();
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}


在这个例子中,代码栈中存在Vector 对象的引用 v 和 Object 对象的引用 o 。在 For 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 o 引用置空。问题是当 o 引用被置空后,如果发生 GC,我们创建的 Object 对象是否能够被 GC 回收呢?答案是否定的。因为, GC 在跟踪代码栈中的引用时,会发现 v 引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。

2.各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。

3.监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

参考链接

参考链接(更为详细)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java