GC收集和内存分配策略

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

    • 判定对象死亡
      • 引用计数法
      • 可达性分析算法
    • ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190310112145178.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE1OTgzNTQ5,size_16,color_FFFFFF,t_70)
      • 方法区回收
    • 垃圾收集算法
      • 标记-清除算法 Mark-Sweep
    • ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019031017215933.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE1OTgzNTQ5,size_16,color_FFFFFF,t_70)
      • 复制算法
      • 标记-整理算法
    • 标记-整理算法的标记过程和标记-清理算法一样,但是后续步骤是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。如下图所示:
    • ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190310185933124.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE1OTgzNTQ5,size_16,color_FFFFFF,t_70)

我们所说的GC主要针对Java 堆, 上文中描述的程序计数器、虚拟机栈、本地方法栈是随着线程而生灭的。栈中的栈帧随着方法的进入和退出有条不紊的执行入栈和退栈,每一个栈帧中分配多少内存也是在编译期就已经确定的。这几个区域不需要过多的去考虑内存回收的问题,因为方法或者线程结束时内存自然就跟着回收了。而Java堆和方法区则不一样, 一个接口的多个实现类可能不一样, 方法的分支需要的内存也不确定,只有在运行期间才能知道多少个对象被创建。GC关注的主要是这部分。

判定对象死亡

Java堆里边存放着几乎所有的实例对象,垃圾收集器的首要任务是确定这些对象中那些存活着哪些已经死亡,GC需要释放这些死亡对象占据的内存。

引用计数法

引用计数法是给每个对象添加一个引用计数器,每当一个地方引用它时就加1;当引用失效时就减1,任何时刻当该对象的引用计数器为0时这个对象就是不可能再被使用的。Java 虚拟机并没有采用引用计数器来管理内存,因为它没办法解决对象之间互相引用的问题。

可达性分析算法

可达性分析算法是主流实现。这个算法的基本实现思想是通过一系列“GC Roots”的对象做为起始点,从这些对象向下搜索,搜索走过的路径叫引用链。当一个对象到 GC Roots没有任何引用链时则说明此对象是不可用的。如下图所示, object5,object6和object7虽然存在关联,但是它们到GC Roots没有引用链,因此它们都是不可用的。


GC收集和内存分配策略_第1张图片

Java中GC Roots引用的对象主要有以下四种:

  • 虚拟机栈中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中引用的对象,一般就是Native方法。

其实在Java虚拟机中,引用并不是简单的被分为引用和未引用两种状态。有时候我们希望能达到这样一种状态,对于一些对象:当内存空间还足够时,则能保留在内存之中;如果垃圾收集后内存还是非常紧张,则可以抛弃这些对象。JDK1.2 之后java对引用的类型进行了扩充,将引用分为强引用、软引用、弱引用和虚引用。

  • 强引用就是指在程序之中普遍存在的 类似于" Object obj = new Object()" 这类引用,只要强引用还存在,垃圾收集器永远不会回首掉被引用的对象;
  • 软引用是指一些还有用但不是必需的对象。这类对象在内存溢出之前,会将这些对象列进回收范围进行二次回收,如果这次回收后还没有足够内存才会抛出异常。
  • 弱引用更弱相对于软引用,下一次垃圾收集无论如何都会对这类对象进行回收。
  • 虚引用也成幽灵引用,虚引用的存在完全不影响对象的生存周期。

方法区回收

方法区或者永久代中进行垃圾回收的效率一般较低。永久代的垃圾回收主要包含两部分内容:回收废弃的常量和无用的类。假设常量池中存在一个字符串“des”,但是当前系统中没有任何一个String对象是指向“des”的,也没有其他地方引用了这个字面量,如果此时发生内存回收那么这个字面量会被清理出常量池。常量池中的其他类(接口)、方字段的符号引用也类似。

对于类的清理比较复杂,无用类再满足以下三个条件:

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

搞清楚了各种对象被清理的条件,下面将介绍垃圾收集的算法。

垃圾收集算法

标记-清除算法 Mark-Sweep

标记-清除算法是最基础的算法,后续的所有算法都是基于这种思路并对不足加以改进。它主要有两个不足:效率,标记和清除效率都不够高;另一个是空间问题,清除之后会产生大量不连续的内存碎片。内存随便会导致系统在需要为较大的对象分配空间时找不到足够的连续内存而不得不进行下一次收集动作。标记清除算法如下图所示:


GC收集和内存分配策略_第2张图片

复制算法

复制算法的出现是为了解决标记-清除算法的效率问题。它将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完时他就将还存活着的对象复制到另外一块,然后再把使用过的内存一次清理掉。这样的内存划分会导致可以给堆使用的内存为可用内存的一半,虽然实现简单运行高效,但是代价未免太大。

现在的商用虚拟机都采用这种方法来回收新生代,但是内存的划分并不是1:1,而是将内存划分为一块较大的Eden空间和两块较小的Survivor空间。每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存在的对象一次性复制到另一块Survivor上,然后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机Eden 和survivor的比例为8:1。 当Survivor的内存空间不够使用时则需其他内存(这里指老年代)进行分配担保。

标记-整理算法

标记-整理算法的标记过程和标记-清理算法一样,但是后续步骤是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。如下图所示:

GC收集和内存分配策略_第3张图片

你可能感兴趣的:(JVM)