深入理解java虚拟机——垃圾回收机制与内存分配

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhangjingao/article/details/86680827

深入理解java虚拟机——垃圾回收机制与内存分配

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhangjingao/article/details/86680827

  java良好的java回收机制一直是c++语言程序员羡慕的地方,当然这有利有弊,内存动态分配和垃圾回收技术一直都是java和c++之间的一睹高墙。
  垃圾回收主要针对的区域是java堆区和方法区,其他属于线程私有,随线程结束而清理。
  垃圾回收算法回收首先需要判断对象死了没??

对象死了没

  判断对象已死算法有两种:引用计数法,可达性分析法

引用计数法

  思想:给对象添加一个引用计数器,每当有一个地方引用了该对象就给对象引用计数器+1,引用失效则-1,当引用为0时,对象就不可能再被引用了。
  优点:实现简单,判断的效率高。
  缺点:无法解决对象之间循环引用的问题,在这个情况下会导致内存泄露。(主流的虚拟机没有选择引用计数法的)

可达性分析法

  思想:通过建立一些“GC Roots”对象作为起始结点,从这个结点开始向下搜索,搜索的路径称为引用链,当一个对象和GCRoots之间没有引用链时,表明这个对象不可达,证明这个对象已经不可用了。
可作为GC Roots的对象有以下四种:
· 虚拟机栈(局部变量表)中引用的对象
· 方法区中类静态属性引用的对象
· 方法区中常量引用的对象
· 本地方法区中Native方法(JNI)引用的对象

  在可达性分析法中,无引用链也并非真的已死,或许在finalize方法中可再次被引用。finalize方法是用来在垃圾回收前清理对象所占用的系统资源的。
  可达性分析法在对象无引用链时会至少标记两次对象才会宣告对象死亡,第一次无标记判断对象有没有必要之星finalize方法,当对象覆盖了该方法或者没有被虚拟 机调用过(只调用一次),那么就会执行finalize方法,如果没必要那么就等待第二次标记。需要调用finalize方法的对象会被放在F-queue队列中,如果对象在finalize方法中自救了自己,那么就移出队列,如果没有自救,那就等待回收。由于finalize只会被调用一次,所以对象只有一次自救的机会。但是该方法强烈不建议使用。
  引用有4种:强引用(new,不会回收),软引用(将要发生内存溢出异常前回收),弱引用(下次垃圾回收前回收),虚引用(丝毫不影响垃圾回收,回收前系统会通知下)

回收方法区(永久代)

  回收方法区主要回收的内容有两个:

  1. 回收废弃常量
    常量池中的常量未被任何对象引用
  2. 回收无用的类(条件苛刻)
    2.1 该类所有实例被回收
    2.2 加载该类的类加载机制被回收
    2.3 该类的Class对象没有被引用,保证不会被反射机制访问

怎么处理已死的内存部分(垃圾回收算法)

  上述引用计数法和可达性分析法是判断对象是否死了,那死了之后怎么回收呢?

标记-清除算法

  分为两步,首先标记可回收对象,然后统一将可回收对象清除掉。
  优点:简单,是最基础的回收算法,后面很多都是基于其上补充完善的。
  缺点
  1. 效率低:标记和清除的效率都不高;
  2. 空间不连续:由于可回收对象较为分散,回收后内存空间不连续,有很多内存碎片,导致后期为大对象分配空间时又不得不再次回收内存。

复制算法

  将内存分为大小相等的两块,每次只使用其中的一块,当这块内存不足以分配空间时,会将这块内存中还存活的对象复制到另一块内存上,再把使用过的空间一次性清理掉。
(虚拟机使用这种方式回收新生代,HotSpot虚拟机将内存分为一块较大的Eden区和两块较小的Survivor区,默认大小是: 8:1:1。每次为新生代分配的空间是Eden区和一块Survivor区,90%(80%+10%),当回收垃圾时,将两块区域的存活对象复制到另一块Survivor上,并清理掉那两块区域,如果剩余的Survivor区域空间不足以容下存活对象,那么将依赖于老年代分担。)
  优点:
  1. 实现简单,运行高效
  2. 无空间碎片,为对象分配空间时只需移动堆顶指针即可
  缺点:
  代价太大,每次只能使用一半的内存

标记-整理算法

  如果存活对象较多,对于复制算法而言需要复制的太多对象,降低了效率,而且可以完全利用100%的空间。适用于老年代的垃圾回收。
  标记阶段和标记-清除算法一样,然后将存活对象移动到内存边界线处,而后对除边界外的内存进行清理。

分代收集思想

  一种垃圾回收思想,对不同内存根据所处区域不同使用不同的垃圾回收算法。
  对于新生代,对象朝生夕死,存活时间短,只有少量存活,而且有老年代作为内存担保,所以使用复制算法。
  对于老年代,由于对象存活时间长,没有额外空间作为担保,所以使用标记-清除或标记-整理算法。

垃圾回收算法实现

  在安全点上生成OopMap用来记录引用位置,完成GCRoots枚举,当GC发生时,除执行JNI调用的线程外,其余线程必须尽快到达最近的安全点上停止运行(GC停顿)。
  GC停顿分为两种:
  1. 抢先式中断(没有虚拟机在用了):当GC发生时,所有线程全部中断,发现有线程不在安全点,那么就恢复线程,使其到达安全点。
  2. 主动式中断:GC需要中断线程时,不对线程操作,设置一个标志,所有线程执行时必须主动轮训标志,当其为真时就主动挂起自己。(轮询标志的地方和安全点是重合的)

对象分配和回收策略

  • 对象优先在Eden分配,当Eden空间不够时会发起一次MinorGc,还不够会将存活对象移入担保区(老年代)。
  • 大对象直接进入老年代
  • 长期存活的对象进入老年代
      每个对象有一个对象年龄计数器,如果对象在Eden出生并在一次MinorGc之后并在Survivor区存活(如果进入了老年代存活,那就没必要在考虑了),就将对象年龄计数器加1,之后每经过一次MinorGc,对象的年龄就会加1,加到一定年龄(默认15)对象就会进入老年代。
  • 动态对象年龄判断
      并不一定必须达到一定年龄才能晋升老年代,如果在Survivor区中相同年龄所有对象的大小总和大于survivor区空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
  • 空间分配担保
      在进行MinorGc之前,会判断下老年代的剩余的最大可用连续空间是否大于新生代中所有对象总空间,如果大于,那么认为MinorGc就是绝对安全的。
      如果小于,首先判断虚拟机中HandlePromotionFailure是否允许担保失败,
      如果允许,那么判断老年代最大可用连续空间是否大于之前MinorGc晋升到老年代的对象的平均大小,如果大于,那么这次MinorGc就是有风险的,但是允许冒险。
    如果小于、或者不允许担保失败那么就进行一次FullGc。

猜你喜欢

转载自blog.csdn.net/zhangjingao/article/details/86680827