JVM基础学习(二):内存分配策略与垃圾收集技术

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来

垃圾收集概述

Java内存模型中的堆和方法区是垃圾收集技术所需要关注的终点,因为其他的区域会跟随线程的结束而自动回收。

而需要解决垃圾收集的首要目标便是解决如何判断一个对象已经不需要了从而自动进行回收;判断对象是否可以进行回收的算法可以分为引用计数算法可达性分析算法

对于Redis有一些了解的同学应该知道Redis的对象内存回收算法便是使用的引用计数算法;而JVM都是使用的可达性分析算法,在此我们只讨论可达性分析算法。

可达性分析算法

可达性分析算法简而言之便是从一些称为“GC Roots”的根对象作为起始节点集,根据引用关系向下搜索,搜索过的路径称为引用链,若某个对象到任何GC Roots对象都没有引用链,那么此对象便为不可达。
JVM基础学习(二):内存分配策略与垃圾收集技术_第1张图片

在Java技术体系中,固定作为GC Roots对象的包括以下几种:

  • 虚拟机栈中引用的变量(虚栈);
  • 方法区中类静态属性引用的对象(静);
  • 方法区中常量引用的对象(常);
  • 本地方法栈中引用的变量(本栈);
  • JVM虚拟机内部的引用(内);
  • 所有被同步锁持有的对象(锁);
  • 其他反映虚拟机内部情况的对象;

垃圾收集算法

从如何判定对象消亡的角度出发,垃圾收集算法可以划分为“引用计数式垃圾收集(直接垃圾收集)”与“追踪式垃圾收集(间接垃圾收集)”。

分代收集理论

分带收集理论是基于以下分代假说之上:

  • 弱分代假说:绝大多数对象都是朝生夕灭的;
  • 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡;
  • 跨代引用假说:跨代引用相对于同代引用来说仅占极少数;

以上的假说共同奠定了垃圾收集器的一致的设计原则:收集器应该将Java堆划分为不同的区域,然后根据回收对象的年龄分配到不同的区域中存储。
所以现代JVM垃圾收集器一般将Java堆划分为“新生代”与“老年代”;
针对不同分代的GC算法有以下几种:

  • 部分收集(Partial GC):不是完整收集整个Java堆的垃圾收集;
    • 新生代收集(Minor GC/Young GC):目标只是新生代的垃圾收集;
    • 老年代收集(Major GC/Old GC):目标只是老年代的垃圾收集,暂时只有CMS收集器实现了单独的老年代收集;
    • 混合收集(Mixed GC):目标是收集整个新生代以及部分老年代的垃圾收集,暂时只有G1收集器有这种行为;
  • 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集;

跨代引用假说主要是解决跨越“新生代”与“老年代”的对象之间引用的问题,根据该假说所衍生出的对于跨代引用的解决方法是:

在“新生代”上建立一个全局的数据结构(记忆集),该结构将老年代划分为若干小块,标识“老年代”中存在跨代引用的内存区域。此后发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会假如GC Roots进行扫描。

垃圾收集算法

垃圾收集算法这里介绍三种:

  • 标记-清除算法
  • 标记-复制算法
  • 标记-整理算法

标记-清除算法

标记-清除算法是最早出现也是最基础的算法,其执行步骤为:

  1. 标记出需要回收的对象(标记);
  2. 统一回收掉所有已被标记的对象(清除);
  3. 或者第一步标记出存活的对象,第二步清理没有被标记的对象;

标记-清除算法的缺点如下:

  1. 执行效率不稳定,与需要被标记的对象数量相关;
  2. 内存碎片化问题,标记、清除之后会产生大量不连续的内存碎片,可能会导致后续如果需要分配较大对象时无法找到足够的连续对象从而触发另外一次GC;

标记-复制算法

标记-复制算法简称复制算法,解决的是标记-清除算法中面对大量可回收对象执行效率低的问题。
最早的标记-复制算法是一种称为“半区复制”的算法,其将可用内存按照容量大小划分为大小相等的两块,一块用以平时使用,另外一块用以GC时复制还存活的对象。
标记-复制算法是现在的商用Java虚拟机常用的算法,但是“半区复制”算法所浪费的内存过多达到了整个内存区域的一半,所以后续又进行了很多的改进;

Appel式回收

“Appel式回收”是一种更优化的半区复制分代策略,其将内存区域分为一块较大的Eden空间与两块较小的Survivor空间,每次分配内存的时候只使用一块Eden空间与一块Survivor空间;
“Appel式回收”的具体做法是:

存放对象的时候只会使用Eden空间与一块Survivor空间,然后垃圾收集的时候会将存活的对象移到另外一块未被使用的Survivor区域。
其实就相当于是将存活对象一直有一个区域可以存放,这样便避免了内存空间的浪费。
HotSpot虚拟机的Serial、Parnew等新生代收集器均采取了这种策略设计新生代的内存布局。

标记-整理算法

标记-整理算法是针对老年代对象的存亡特征所提出的有针对性的算法。标记步骤并没有进行改变,整理步骤时,是将所有存活的对象都向内存对象一端移动,然后直接清理掉边界以外的内存。
若移动存活对象,那么便在对象移动过程中必须全程暂停用户应用程序才能进行,被称为“Stop The World”。
若不移动对象,那么内存碎片化问题只能依赖更为复杂的内存分配器与内存访问器解决。

总结

以上便是内存分配策略与垃圾收集技术的理论基础,下一篇博客介绍现代JVM的实现细节。

你可能感兴趣的:(JVM基础学习(二):内存分配策略与垃圾收集技术)