垃圾收集算法与内存分配策略--《深入理解Java虚拟机》学习笔记

时间:2023-01-02 09:09:11

垃圾收集算法与内存分配策略–《深入理解Java虚拟机》学习笔记

一、对象存活算法判定

1、何为引用
如果reference类型的数据中存储的数值代表另一块内存的起始地址,则称这块内存代表着一个引用;有一类对象,当内存空间还足够时,则能保留在内存之中,如果内存空间在进行垃圾收集后还是很紧张,则根据引用类型抛弃这些对象,进行回收;引用类型如下:

  • 强引用:普遍存在,类似于“Object obj = new Object()”引用,存在强引用,垃圾收集器就不会回收掉被引用的对象;
  • 软引用:描述有用但非必须的对象,在系统发生内存溢出异常前,软引用的对象被列入回收范围进行二次回收,当这次回收后内存仍然不够,系统会将这部分对象回收(二次回收),如果还是不够,才会跑出内存溢出异常,SoftReference类可实现软引用;
  • 弱引用:描述非必须对象,被弱引用的对象只能生存到下次垃圾收集之前,WeakReference类可实现弱引用;
  • 虚引用:强度最弱,无法通过虚引用获取到对象,唯一目的是在这个对象被收集器回收时收到一个系统通知;

2、判断对象存活算法(是否需要回收该对象内存):引用计数算法、可达性分析算法

  • 引用计数算法:给对象添加一个引用计数器,每当一个地方引用它,计数器加1,当引用失效时,计数减1,计数器为0时这个对象就不可能再被使用了(已死);该方法因为无法解决相互引用对象,所以主流虚拟机不常用该方法;
  • 可达性分析算法:通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达)时,则该对象不再被使用;可作为GC Roots的对象如下:
    • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
    • 方法区中类静态属性引用的对象;
    • 方法去中常量引用的对象;
    • 本地方法栈中JNI引用的对象;

3、回收方法区
方法区的垃圾收集主要包括两部分内容:废弃常量、无用的类;废弃常量指没有引用的类,无用的类的判断方法如下:

  • 该类所有的实例均已被回收,也就是java堆中不存在该类的任何实例;
  • 加载该类的ClassLoader也已经被回收;
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射调用该类的方法;

二、垃圾收集算法

  1. 标记-清除算法
    垃圾收集算法与内存分配策略--《深入理解Java虚拟机》学习笔记
    最基础的“标记-清除”算法,分为两个阶段:标记出需要回收的对象,然后统一回收。该算法缺点如下:

    • 效率问题:标记和清除两个过程效率都不高;
    • 空间问题:标记清除后会有大量不连续的内存碎片;
  2. 复制收集算法
    垃圾收集算法与内存分配策略--《深入理解Java虚拟机》学习笔记
    将内存按容量分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,将还存活着的对象复制到另一块内存上,然后再把已使用过的内存空间一次清理掉;这种算法每次都只是对整个半区内存回收,切不用考虑内存碎片等问题,简单高效,常采用这种算法来回收新生代,缺点如下:

    • 内存容量:缩小为原来的一半;
    • 效率问题:对象存活率较高时需要进行大量复制,效率低下;
  3. 标记-整理算法
    垃圾收集算法与内存分配策略--《深入理解Java虚拟机》学习笔记
    同标记-清除算法类似,先标记回收的对象,然后让存活的对象向一边移动,然后直接清理掉边界以外的对象;

  4. 分代收集算法
    根据对象存活周期的不同将内存划分为几块,一般讲java堆内存分为新生代和老年代,根据各个年代的特点采用合适的收集算法;新生代存活率低通常采用“复制”算法,老年代存活率高通常采用“标记-清理”或者“标记-整理”算法;

三、内存分配
java中,堆内存被划分为不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。新生代进行GC采用的算法是“复制算法”, 老年代GC采用的算法是“标记-清除算法”。
垃圾收集算法与内存分配策略--《深入理解Java虚拟机》学习笔记

  • 对象优先在Eden分配:当Eden区没有足够空间分配时,虚拟机将发起一次Minor GC(新生代gc);
  • 大对象直接进入老年代:大对象指需要大量连续内存空间的java对象,如很长的字符串及数组;虚拟机提供了一个参数 -XX:PretenureSizeThreshold,当对象所占内存大于该值时直接放入老生代存储;
  • 长期存活的对象进入老年代:为了区分对象处于新生代还是老年代,虚拟机给每个对象定义了对象年龄计数器;如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳,将被转移到Survivor,对象年龄设为1;对象每在Survivor区中度过一次Minor GC,年龄增加1,当年龄到达晋升老年代的年龄阈值(默认15岁),将会被晋升到老年代。年龄阈值可以通过 -XX:MaxTenuringThreshold设置;
  • 动态对象年龄判定:为更好适应不同程序的内存,虚拟机不是要求对象年龄必须大于阈值才能进入老年代;如果在Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代;
  • 空间分配担保:在Minor GC之前,虚拟机先检查老年代最大可用的连续存储空间是否大于新生代所有对象总空间,如果成立,那么该次Minor GC确保为安全的。如果不成立,虚拟机查看HandlePromotionFailure值判断是否允许担保失败,如果允许,继续检查老年代最大可用的连续存储空间是否大于历次晋升到老年代对象的平均大小,大于,则尝试进行Minor GC(有一定的风险);小于,或者HandlePromotionFailure值设置不允许,则进行一次Full GC(老年代GC);