java架构师 JVM性能优化之垃圾回收机制

1、垃圾回收机制简介

        不定时去堆内存中清理不可达对象(没有被引用的对象或没有存活的对象)。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,即使程序员能明确地判断出有一块内存已经无用了,是应该回收的,程序员也不能强制垃圾收集器回收该内存块。程序员唯一能做的就是通过调用System.gc 方法来"建议"执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。这也是垃圾收集器的最主要的缺点。

案例:

public class Test {
	
    public static void main(String[] args) {
		Test test = new Test();
		test = null;//不可达对象,满足jvm回收条件
		System.gc(); // 手动回收垃圾
	}

	@Override
	protected void finalize() throws Throwable {
		// gc回收垃圾之前调用
		System.out.println("垃圾回收机制...");
	}
}

1.1 finalize方法作用

        Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

2、新生代与老年代

        Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。

        在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。

        新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。

        这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。

堆的内存模型大致为:

        在java JDK1.6中,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。

        根据垃圾回收机制的不同,Java堆有可能拥有不同的结构,最为常见的就是将整个Java堆分为新生代和老年代。其中新生代存放新生的对象或者不常使用的对象,老年代则存放比较活跃的对象(经常被引用的对象)。新生代分为eden区、s0区、s1区,s0和s1也被称为from和to区域(为了复制算法将from区的对象交换到to区),他们是两块大小相等并且可以互相角色的空间。绝大多数情况下,对象首先分配在eden区,在新生代回收后,如果对象还存活,则进入s0或s1区,之后每经过一次新生代回收,如果对象存活则它的年龄就加1,对象达到一定的年龄后,则进入老年代。

3、判断对象是否存活

3.1 引用计数法(使用较少)

        引用计数法就是如果一个对象没有被任何引用指向,则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。

        算法实现:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1。任何时刻计数器值为0的对象就是不可能再被使用的。

        缺点:很难解决对象之间相互循环引用的问题。

3.2 根搜索法

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

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

3.2.1 GCRoots的对象

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

4、垃圾回收机制策略

4.1 标记清除算法

(1)概念: 该算法有两个阶段(1) 标记阶段:找到所有可访问的对象,做个标记;(2)清除阶段:遍历堆,把未被标记的对象回收。

(2)应用场景:该算法一般应用于老年代,因为老年代的对象生命周期比较长。

(3)优缺点

◆ 优点:
    - 可以解决循环引用的问题 
    - 必要时才回收(内存不足时) 
◆ 缺点: 
    - 回收时,应用需要挂起,也就是stop the world 
    - 标记和清除的效率不高,尤其是要扫描的对象比较多的时候 
    - 会造成内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到)

4.2 复制算法

(1)概念: 如果jvm使用了复制算法,一开始就会将可用内存分为两块,from域和to域, 每次只是使用from域,to域则空闲着。当from域内存不够了,开始执行GC操作,这个时候,会把from域存活的对象拷贝到to域,然后直接把from域进行内存清理。

(2)应用场景:复制算法一般是使用在新生代中,因为新生代中的对象一般很快就不被引用,且存活对象的数量并不多,这样使用复制算法进行拷贝时效率比较高。jvm将堆内存划分为新生代与老年代,又将新生代划分为Eden(伊甸园) 与2块Survivor Space(幸存者区) ,然后在Eden –>Survivor Space 以及From Survivor Space 与To Survivor Space 之间实行复制算法。 不过jvm在应用复制算法时,把内存按照8:1划分。也即是说,Eden区:From区:To区域的比例是始终有90%的空间是可以用来创建对象的,而剩下的10%用来存放回收后存活的对象。

(1)当Eden区满的时候,会触发第一次young gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发young gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。

(2)当后续Eden又发生young gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。

(3)可见部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。

注意: 万一存活对象数量比较多,那么To域的内存可能不够存放,这个时候会借助老年代的空间。

(3)优缺点

优点:在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除中导致的引用更新问题。

缺点: 会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,coping的性能会变得很差。

4.3 标记压缩算法。

(1)概念:标记清除算法和标记压缩算法非常相同,但是标记压缩算法在标记清除算法之上解决内存碎片化。

(2)基本思路:

任意顺序 : 即不考虑原先对象的排列顺序,也不考虑对象之间的引用关系,随意移动对象;
线性顺序 : 考虑对象的引用关系,例如a对象引用了b对象,则尽可能将a和b移动到一块;
滑动顺序 : 按照对象原来在堆中的顺序滑动到堆的一端。

(3)优缺点:解决内存碎片问题,缺点压缩阶段,由于移动了可用对象,需要去更新引用。

4.4 分代算法

(1)概念:

        根据对象的存活周期的不同将内存划分成几块,新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

        新生代对象朝生夕死,对象数量多,只要重点扫描这个区域,那么就可以大大提高垃圾收集的效率。老年代对象存储久,无需经常扫描老年代,避免扫描导致的开销。

(2)新生代:在新生代,每次垃圾收集器都发现有大批对象死去,只有少量存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;

(3)老年代:老年代中对象存活率高、没有额外空间对它进行分配担保,就必须“标记-清除-压缩”算法进行回收。新创建的对象被分配在新生代,如果对象经过几次回收后仍然存活,那么就把这个对象划分到老年代。老年代区存放Young区Survivor满后触发minor GC后仍然存活的对象,当Eden区满后会将存活的对象放入Survivor区域,如果Survivor区存不下这些对象,GC收集器就会将这些对象直接存放到Old区中,如果Survivor区中的对象足够老,也直接存放到Old区中。如果Old区满了,将会触发Full GC回收整个堆内存。

5、Minor GC和Full GC区别

5.1 概念

        新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
        老年代 GC(Major GC  / Full GC):指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢10倍以上。

5.2 Minor GC触发机制

         当年轻代满时就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发

5.3 GCFull GC触发机制

        当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代,当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载其中Minor GC如下图所示:


        虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold (阈值)来设置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值