垃圾回收和内存分配策略

垃圾回收和内存分配策略

​ 由于程序计数器、虚拟机栈、本地方法栈随线程而生,随线程而灭一般的垃圾回收指Java 堆和方法区出的内存回收。

1 如何判断对象是否已死

1.1 引用计数器

​ 给对象添加一个引用计数器,当有一个应用引用他时,计数器加一,引用失效时减一。

​ 无法解决对象间相互循环引用的问题,Java 未使用。

1.2 可达性分析

​ 通过一系列称为“GC Root"的对象作为起点,向下搜索,搜索所走的路径称为引用链。当一个对象没有任何引用链相连时,证明此对象是不可用的。

​ GC Root 对象包含如下:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即,native方法)引用的对象

1.3 回收过程

​ 如

1.4 常见的垃圾回收算法

  • 标记 - 清除 算法

    • 原理:

      ​ 标记所有需要回收的对象,然后统一回收

    • 缺陷

      ​ 效率问题,标记和清除的效率都不高;标记清除后有大量的不连续内存碎片。

  • 复制算法

    • 原理

      ​ 将内存划分为新生代和老年代,新生代分为 一块较大的Eden 空间和两块较小的 Survicor 空间,每次只使用Eden 空间和一块Survivor 空间,然后把已使用的那块一次清理。新生代的收集全部采用复制算法。

  • 标记 - 整理 算法

    • 原理

      ​ 先标记需要回收的对象,然后让所有的存活的对象向一段移动,然后清理边界以外的内存。针对存活率较高时候的情形,因为复制代价会比较大。

  • 分代收集

    ​ 在新生代,每次回收都会有大量对象死亡,所以使用 复制算法,老年代对象存活率较高,所以使用“标记-清理” 或 “标记-整理” 算法。

1.5 堆里面的分区:Eden、servival to 、from to、老年代

  • Eden 区

    ​ 位于Java 堆的新生代。新生代中的对象寿命比较短。

    ​ 大多数情况下对象有现在的Eden 区分配,如果启动了TLAB (本地线程分配缓冲)则优先在TLAB上分配。如果 Eden 区内存也用完了,则会进行一次 Minor GC。

  • Servivor 区

    ​ 复制算法将内存分为Eden 、 from 、 to survivor 三个区域,使用的时候每次只使用Eden 和 from survivor 区域,当回收时将Eden 和 from survivor 区中活着的对象复制到to survivor 区上。

  • 老年代

    ​ 里面存放都是存活时间交久的对象:

    ​ 如果大量连续内存空间的大对象直接进入老年代、长期存活的对象将进入老年代。

    ​ 当老年代容量满的时候,会触发一次full GC,回收老年代和年轻代中不再使用的对象资源。

  • TLAB

    ​ JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)。默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。
    ​ 也就是说,Java中每个线程都会有自己的缓冲区称作TLAB(Thread-local allocation buffer),每个TLAB都只有一个线程可以操作,TLAB结合bump-the-pointer技术可以实现快速的对象分配,而不需要任何的锁进行同步,也就是说,在对象分配的时候不用锁住整个堆,而只需要在自己的缓冲区分配即可。

  • Java对象分配的过程

    1. 编译器通过逃逸分析,确定对象是在栈上分配还是在堆上分配。如果是在堆上分配,则进入选项2.
    2. 如果tlab_top + size <= tlab_end,则在在TLAB上直接分配对象并增加tlab_top 的值,如果现有的TLAB不足以存放当前对象则3.
    3. 重新申请一个TLAB,并再次尝试存放当前对象。如果放不下,则4.
    4. 在Eden区加锁(这个区是多线程共享的),如果eden_top + size <= eden_end则将对象存放在Eden区,增加eden_top 的值,如果Eden区不足以存放,则5.
    5. 执行一次Young GC(minor collection)。
    6. 经过Young GC之后,如果Eden区任然不足以存放当前对象,则直接分配到老年代。

    ​ 对象不在堆上分配主要的原因还是堆是共享的,在堆上分配有锁的开销。无论是TLAB还是栈都是线程私有的,私有即避免了竞争(当然也可能产生额外的问题例如可见性问题),这是典型的用空间换效率的做法。

1.6 Minor GC 和 Full GC

  • Minor GC(新生代 GC ):大多数情况下,对象在新生代Eden 区中分配,当Eden 区中没有足够空间时候,虚拟机触发一次Minor GC。Minor GC 非常频繁,速度快。
  • Full GC
    • 在 Minor GC 之前会检查老年代中最大可用连续空间是否大于新生代中的对象,如果成立,则这次Mior GC 是安全的,否则要查看是否允许担保失败,如果不允许则改进为Full GC ,允许的话尝试一次Minor GC
    • 老年代满时,也会触发Full GC
原文地址:https://www.cnblogs.com/moongeek/p/7588023.html