你的位置:首页 > Java教程

[Java教程]深入理解java虚拟机(2)


GC可谓是java相较于C++语言,最大的不同点之一。

1.GC回收什么?

上一篇讲了内存的分布。

其中程序计数器栈,虚拟机栈,本地方法栈 3个区域随着线程而生,随着线程而死。这些栈的内存,可以理解为在编译期已经确定。

方法结束,或者线程结束时,内存就自然被回收了。

 一个interface的多个实现类,需要的内存可能不一样,一个方法的多个分支需要的内存也不一样,我们只有在程序运行的时候,才知道会创建那些对象,需要多少内存。

这部分分配和回收都是动态的,GC所关注的就是这部分内存。

2.回收的标准:

java堆里面几乎存放着所有的对象实例。对象实例如果已经不再被使用,那这段内存就应该被回收,这个时候就是GC上场的时候。

 

2.1 引用计数法:

给对象一个引用计数,当计数为0的时候,可以理解为,该对象不再被使用,可以释放内存。

但是这个方法很难解决一个问题:对象间的相互引用问题。

举个例子:

对象objA和objB都有字段instance。

objA.instance = objB;

objB.instance = objA;

它们没有其他引用。

但是它们的引用计数不可能为0.于是无法通知GC回收它们。

2.2 可达性分析算法:

主流的商用程序语言的主流实现中,都称为可达性分析。

这个算法的基本思想通过GC Roots的对象作为起始点。当一个对象,到起始点,没有连接的时候,可以理解为,这个节点的内存可以释放。

从图中可以看到,object5,object6,object7 这些对象会被GC回收。

 

2.3 引用

无论通过何种算法来实现GC,都和引用有关。

java1.2之后,引用分为 强引用,软应用,弱引用,已经虚引用。

强引用是java普遍存在的一种状态,类似new 一个对象。强引用不会被GC回收。

软引用,软引用是描述还有用,但未必要存在的对象。它的生存时间是,当内存不足时,也就是快要OOM的时候,GC会回收它。

弱引用,就是非必要的的对象。被弱引用指向的对象,只能生存到下一次GC之前。

虚引用,虚引用的目的是为了当GC发生时,可以收到一个系统通知。不会对引用对象产生任何影响。

2.4 对象如何判断要回收:

GC会对不可达的对象,做第一次标记。

当该对象执行finalize方法后,或者没有覆盖finalize方法,这回被第二次标记,这个时候,GC会被这段内存清理。

当对象执行finalize方法时,JVM会把它放在一个F-queue里面,这是这个对象实例拯救自己的最后机会。

它只需要在finalize里面,把this赋给某个变量。

任何一个对象finalize只会被执行一次。

 

3.垃圾收集的算法

 

3.1标记清楚算法:

先标记需要回收的内存,标记完成后,统一回收。

这个是最基础的算法,其他算法都是以此为基础。

3.2 复制算法

就是将内存分为大小相等的2块,每次只使用一块,当一块用完的时候,就将还存活的部分,复制到另一块空间,然后

把已经使用的那块做一次性清理。

这样就不用考虑内存碎片的情况。只是这种算法代价就是一半的内存空间。

3.3 标记--整理算法

让所有存活的对象移向一端,然后直接清理掉端边界以外的内存。

3.4 分代算法

根据对象存活周期的不同,分为新生代,和老年代。

在新生代,每次回收都有很多对象被回收,可以选用复制算法,只需要少量存活的对象复制成本就可以。

而老年代,存活的时间比较久,必须使用标记清楚或者标记整理方法。

3.4.1 枚举根节点

在选择处理GC Roots方法时,GC Root所在的上下文可能有数百兆,所以在判断GC Roots的时候,应该确保一致性,JVM

应该停止所有java执行线程。所以GC是占用CPU作为代价!

 

4.内存分配策略

4.1对象优先在Eden分配

大多数情况下,对象在新生代的Eden区中分配内存,当Eden区内存不足时,java虚拟机将发生GC,

释放不需要的对象。

4.2 大对象直接在老年代

大对象,是指需要大量连续空间的对象,比如很长的字串或者数组。

比大对象更糟糕的事情就是,遇到一群很快无用的 大对象。

4.3 长期存活的对象将进入老年代

对象开始放在新生代eden区,如果度过一次GC,进入Survivor空间,对象年龄设置为1.

以后,每一次GC,年龄就加1。

当年龄到达一定的阀值以后,就回进入老年代。