Java虚拟机【2】

垃圾收集器与内存分配策略

程序计数器、虚拟机栈、本地方法栈三个区域随线程生灭,栈中的栈帧的随着方法进入/退出,且分配的内存大小在类结构确定后就已知,因此这些区域的内存分配回收确定。需要考虑CG的是Java堆和方法区,一个接口中的多个实现类需要的内存不同,方法的多个分支需要的内存也不同。因此这部分是垃圾收集器关注的。

1.确认对象“死活”

垃圾收集器回收“死去”的对象,因此需要判断对象的“死活”,方法:1.引用计数法 2.可达性分析算法

1.1.引用计数法

给对象添加引用计数器,有个一地方引用它,计数+1,引用失效计数-1,计数为0的对象不可能再被使用。

缺点:难以解决对象循环引用的问题(如下例子)

public class ReferenceCountingGc{
    public Object instance = null;
    private static final int _iMB = 1024*1024; 
    private byte[] bigSize = new byte[2*_1MB];// 占内存用,以便在GC日志中看是否被回收
    
    public static void testGC(){
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;
        System.gc(); // 由于两个对象互相引用,因此引用计数不为0,无法回收   
    }
}
扫描二维码关注公众号,回复: 8104984 查看本文章

1.2.可达性分析算法

基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

在Java语言中,可以作为GCRoots的对象包括下面几种:

  1. 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
  2. 方法区中的类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(Native方法)引用的对象。

若不可达对象覆盖了finalize方法或其已被虚拟机调用过,则对象将逃脱一次“死亡”,只需将其与引用链上任一对象建立关联即可在第二次GC标记时免于“死亡”。

1.3.引用

原先的引用是对象只有引用与被引用两种状态,对于一些不太“重要”的对象来说就过于僵硬了,因此通过添加强引用、软引用、弱引用、虚引用方法使得内存空间若足够,就保留在内存中,若GC后内存不足就抛弃。

强引用:如Object obj =- new Object()

软引用:在发生内存溢出之前,将其回收

弱引用:弱引用的对象只能活到下一次GC过程之前,不论内存足够与否,都回收掉

虚引用:虚引用的存在不影响其生存时间。仅用来当对象被收集器回收后收到系统通知。

1.4.方法区回收

判断常量是否“废弃常量”的方法:

  1. 类的所有实例都被回收,Java堆中不存在该类的任何实例
  2. 加载的ClassLoader被回收
  3. 该类的java.lang.Class对象未在任何地方被引用(包括反射)

例:String对象“abc”进入常量池,但没有任何String对象引用这个常量,则发生内存回收时会回收这个对象

2.垃圾收集算法

 2.1.标记—清楚算法

算法分为“标记”和“清除”两个阶段。标记阶段将需要回收的对象标记,在清除阶段进行清除

缺点:1.标记与清除的效率都不高。2.清除后会产生大量不连续的碎片,当需要分配较大的对象时,会提前触发另一次垃圾收集

2.2.复制算法(基于2.1)(常用

将容量分为等量的两块,每次用其中的一块,当内存用完了,把还存在的对象复制到另一块上,再把已经使用过的内存空间一次清理掉,因此每次对整个半区进行内存回收

优点:效率高

缺点:内存搜小为了原来的一半,复制操作效率低

现代的商用虚拟机采用此种方法。由于98%的对象“朝生夕死”,因此内存分为较大的Eden空间和较小的Survivor空间。回收时,将Eden和Survivor中还活着的对象复制到另一块Survivor中,然后清除之前用的空间。若Survivor空间不够用,用老年代进行担保,对象直接通过分配担保机制进入老年代。

2.3.标记-整理算法(基于2.1)

相比标记—清除,在清除过程中不对可回收对象进行清理,而是让存活的对象向一端进行移动,而后清理掉边界外的内存。

2.4.分代收集算法

根据对象存活周期不同,将内存分为几块(新生代/老生代),根据每个年代的特点采用适合的算法。如死得多的用复制算法,活的多的用2.1或2.2

3.内存分配回收策略

总的说,在堆上分配,主要分配在新生代的Eden上,若Eden区没有足够空间,发生MinorGC(新生代的GC)发生,若还不够,将原有的对象担保到老年区去。

大对象进入老年代

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

PS:老年代GC相较Minor GC慢10倍以上(Full GC/Major GC)

猜你喜欢

转载自www.cnblogs.com/tillnight1996/p/12000082.html
今日推荐