Java虚拟机垃圾收集器与内存分配策略

Java虚拟机垃圾收集器与内存分配策略

概述

  • 那些内存须要回收,什么时候回收。怎样回收是GC须要完毕的3件事情。
  • 程序计数器。虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性,内存随着方法结束或者线程结束就回收了。

  • java堆与方法区在执行期才知道创建那些对象,这部分内存分配是动态的。本章笔记中分配与回收的内存指的就是:java堆与方法区

推断对象已经死了

  • 引用计数算法:给对象加入一个引用计数器,每当有一个地方引用它,计数器+1;引用失败,计数器-1.计数器为0则改推断该对象已死。

    可是这样的方式,非常难解决java对象间相互循环引用的问题。所以主流的java虚拟机,都没有使用该方式推断对象是否存活。

  • 可达性分析(Reachability Analysis):通过GC Roots作为起点,以这些节点通过引用链(Reference Chain)向下搜索,当GC Roots没有引用链能够到达某个对象A的时候,A能够觉得是不可用对象。这类对象就能够判定为可回收对象
1.引用
  • 引用的狭隘定义:当reference类型数据中存储的数值是还有一块内存的起始地址,就称这块内存代表着一个引用。

    可是这样的方式不能描写叙述一些“食之无味,弃之可惜”的对象

  • JKD1.2之后对引用概念做了扩充,对象分为强引用(Strong Regerence),软引用(Soft Reference)。弱引用(Weak Reference),虚引用(Phantom Reference)
  • (1)强引用指new这类的引用,仅仅要强引用还在。收集器永远不会回收掉被引用的对象
  • (2)软引用用来描写叙述一些还实用,可是非必须的对象。在系统要发生内存溢出的时候,会对这类对象二次回收,假设空间还不够。抛出OOM
  • (3)弱引用的对象仅仅能生存到下一次收集发生之前。不管内存是否足够。

  • (4)虚引用是最弱的一种引用关系,为一个对象设置虚引用唯一目的仅仅是在这个对象被回收的时候收到一个系统通知。

2.回收方法区
  • Java虚拟机规范说过能够不要求回收方法区(永久代)。在新生代中。垃圾收集一次能够回收70%以上内存。可是在方法区(永久代)的收集效率灰常低。主要收集的是废弃常量与没用的类。

  • 废弃常量指常量池存在一个字符串abc.可是当前系统没有对象引用abc,则abc是能够废弃的常量,能够被回收
  • 没用的类:(1)该类的对象已经被全部回收.(2)载入该类的ClassLoader已经被回收.(3)该类相应的Class对象没有在不论什么地方被引用。而且通过反射訪问。HotSpot提供了 -Xnoclassgc參数控制是否回收没用的类

垃圾收集算法

  • 经典经常使用的收集算法:标记-清除算法(Mark-Sweep),标记-整理(Mark-Compact),复制算法(Copying),主流虚拟机的”分代收集”

1.标记-清除算法

  • 标记清除是最基础的收集算法:1.标记出所须要回收的对象。2.标记完毕后统一回收掉全部被标记的对象。
  • 标记清楚算法不足:1.效率不高。

    2.清除之后产生大量不连续的片段。假设这些不连续不够空间存放之后产生的对象,还会提前触发另外一次收集动作。减少内存效率。

2.复制算法:

  • 复制算法将内存分为两等块A,B,每次使用一块内存比方A,当A内存满了之后,将剩下的存活的对象复制到B内存,一次性清除A。长处:实现简单执行高效。而且不用在考虑相似于标记清除方式留下的内存碎片。

    缺点是:将原来的内存仅仅使用一半,成本太高。

  • 当前主流的商业虚拟机大多採用这样的方式收集新生代。新生代对象98%都是朝生夕死。所以不必要依照1:1分配新生代内存。辟如hotspot的分配为:8(Eden):1(from survivor):1(to survivor)。同一时候使用老年代进行内存分配担保。

3.标记-整理(Mark-Compact)

  • 复制收集算法在对象存活率较高的时候。会产生大量的复制工作。影响性能。

    由于老年代。一般对象比較大,而且存活率高,一般不能使用该算法。

  • 标记-整理算法是依据老年代对象存活率高,对象比較大的特性,提出的。其基本原理与标记清除一致,可是后面步骤不是直接清除,而是让全部存活的对象向一端移动,然后清除掉端边界以外的内存。

4.分代收集算法

  • 将虚拟机内存主要分成新生代与老年代,新生代一般使用复制算法,由老年代进行分配担保。老年代使用标记整理算法进行回收。

HotSpot主要算法实现

  • GC-Root节点找引用链,GC-Root节点一般为常量。静态变量,本地变量表中数据。当应用的方法非常多,找引用链这个操作。将会消耗非常多时间
  • 可达性分析,对时间的敏感还体如今GC停顿上,分析的时候。要求系统是停止的,不能够出现可达性分析过程中,对象引用关系还在不断的变化。即“stop the word”。
  • 当前主流虚拟机都使用的准确式GC:在HotSpot实现中,使用一组称为OopMap的数据结构来达到这个目的。在类载入完毕时候。在JIT(即使编译器)编译过程中。在特定的位置记录下栈中那些位置是引用。

    这样GC在扫描的时候能够直接得知这些信息了。

  • 安全点:在OopMap协助下,HotSpot能够高速准确完毕GC-Root枚举,可是OopMap的内容变化指令非常多。假设为每一条指令都生成相应的OopMap。须要消耗大量额外空间。因此。仅仅有在特定位置(安全点:Safepoint)才干记录这些OopMap信息。方法调用,循环跳转,异常跳转等这些功能的指令才会产生SafePoint。
  • 安全区(safe region):安全区域是引用关系不会变化的一个代码片,线程执行到安全区的时候,首先标记自己进入了安全区,这段时间JVM执行GC的时候,不用管这些状态的线程了;线程出安全区的时候,首先会检查系统是否已经完毕了GC Root的选举,假设完毕了继续执行,假设没有完毕则等待直到收到能够安全离开的信号为止。

HotSpot基本的垃圾收集器

分代算法能够将收集器分为新生代收集器和老年代收集器。

1.新生代收集器主要包括:Serial(串行),ParNew(并行),Parallel Scavenge(并行);2.老年代收集器主要包括:Serial Old,Parallel Old,CMS(Concurrent Mark Sweep)。

  • Serial是最早的,单线程的新生代收集器,在收集动作的时候,必须暂停其他全部的工作线程,直到收集结束。

    该收集器差点儿是单CPU中收集速度最快效率最高的收集器。serial对Client模式下的虚拟机来说依然是非常好的选择,回收新生代一两百兆基本在100毫秒以内,这些停顿全然能够接受

  • ParNew是Serial的多线程版本号,该收集器是Service模式下的虚拟机首选收集器,主要原因是它能够与主流的老年代收集器CMS配合使用。

    该收集器使用的线程数默认与CPU内核一致,

  • Parallel Scavenge 收集器是关注点是吞吐量(吞吐量=执行用户代码时间/(执行代码时间+垃圾收集时间)),停顿时间越短。则用户体验越好。高吞吐量则能够高效利用CPU时间。尽快完毕执行任务。该收集器适合大量后台运算而不须要太多的交互的系统。该收集器也叫做吞吐量优先收集器。
  • Serial Old 採用的是标记整理算法。和Serial收集器一样,都是单线程收集器。

    一般使用于Client执行模式下。

  • Parallel Old是Parallel Scavenge收集器配合使用的老年代收集器,使用的是标记整理算法,假设新生代使用了Paraller Scavenge算法。则老年代仅仅能使用Parallel Old与Serial Old算法。在注重吞吐量系统。优先考虑Paraller Scavenge与Parallel Old算法。

CMS收集器

是当前老年代主流收集器,採用标记清除算法。CMS整个过程主要分为4个步骤:1.初始标记。2.并发标记;3.又一次标记;4.并发清除.当中初始标记,又一次标记仍然须要stop the world。

  • 初始标记仅仅是记录GC Root能够直接关联到的对象,速度非常快,并发标记阶段就是进行GC Roots Tracing。又一次标记阶段则是为了修正并发标记期间用户继续操作变动的那一部分对象的标记记录。
  • 耗时比較长的并发标记与并发清除步骤,能够与用户线程一起工作,不须要stop the world。CMS的长处体如今了并发收集,低停顿。
  • CMS缺点1:对CPU资源非常敏感。在低于4核的时候不建议使用该收集器。CMS默认启动的回收线程数是(CPU数量+3)/4。
  • CMS缺点2:CMS没有办法处理浮动垃圾。由于标记之后。用户线程还在执行,会产生其他垃圾,留在下次收集。

    在jdk1.6之后,CMS收集器启动阈值已经提升至92%,假设老年代剩下的8%不能满足浮动垃圾的分配,则会出现Concurrent Mode Failure.从而导致Full GC产生。

    也能够暂时启用serial old 又一次对老年代收集,可是这样停顿时间会非常长。-XX:CMSInitiatingOccupancyFraction属性设置CMS的启动阈值,而且能够依据实际适当减少启动阈值。

  • CMS缺点3: 使用的是标记清除算法。会产生大量碎片空间。假设无法找到足够大的连续空间分配当前对象。则触发一次full gc。

G1收集器

G1收集器被觉得是HotSpot jdk1.7重要进化特征,是一款面向服务端的垃圾收集器。G1具有下面优势

  • 并行与并发:G1能够更充分应用多CPU,多核环境的硬件优势,减少系统停顿时间。G1能够通过并行的方式,让java程序继续执行。
  • 分代收集:G1不须要与其他收集器配合,能够独立管理整个GC堆。它能够自己採用不同的方式去处理新建的对象和已经熬过多次GC的老对象,以产生更好的收集效果。
  • 空间整合: G1是基于标记整理算法实现的收集器,不会产生大量不连续的碎片空间,这一点比CMS要有非常大提高。
  • 可预測的停顿:减少停顿差点儿是全部当前互联网企业应用的关注点。G1除了追求低停顿外,还建立了可预測停顿时间模型,能让使用这指定在一个长度为M的毫秒时间片段内,收集消耗的时间不长于N毫秒。已经有部分实时的垃圾收集器的特征了。
  • 其他收集器回收范围都是整个新生代和整个老年代,G1将整个堆分成多个大小相等的独立区域(Region),新生代与老年代都是Region的不须要联系的集合。回收的时候,有一个优先列表,优先回收价值最大的Region,保证了G1收集器在有限时间内获取尽可能高的收集效率。
  • Region:每个Region都有一个相应的Remembered Set。

    每一次操作Reference时候假设检查到有引用对象在别的Region中。则记录Remembered Set。将Remembered Set作为一个GC-root。能够保证不会对全堆做扫描,也不会有回收遗漏。

  • G1收集器假设不计算维护Remembered Set操作。能够分为下面步骤:1.初始标记。2.并发标记;3.终于标记。4.筛选回收

内存分配策略

  • 对象优先在Eden分配
  • 大对象直接进入老年代:代码中劲量避免短命的大对象。-XX:PretenureSizeThreshold=3145728 表示大于3M的对象直接在老年代分配。
  • 长期存活的进入老年代:虚拟机个每个对象一个age计数器,对象每经历一次Minor GC存活下来。age+1.达到一定年龄值(默认15),则进入老年代。

    -XX:MaxTenuringThreshold=1表示每次Minor GC之后存活的对象就要进入老年代。

  • 动态推断对象年龄:假设在survivor中同样年龄对象的大小总和大于survivor空间的一半,年龄大于或者等于该年龄的对象直接进入老年代。
  • 空间分配担保:创建对象新生代假设没有足够空间时候,则由老年代分配内存,这就要求老年代有连续的能够存放该对象的空间,否则可能担保失败。从而触发Full GC

小结

该内容主要介绍了几种垃圾收集算法,几种主流虚拟机收集器以及内存分配的主要策略。依据详细场景选择关注停顿点还是关注吞吐量,串行还是并行收集器。是调优的重要部分。同一时候理解内存分配。对于写java代码也有一定的提高。

原文地址:https://www.cnblogs.com/tlnshuju/p/7371953.html