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

3.1概述

主要问题:

  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

其中程序计数器,虚拟机栈,本地方法栈随线程而生,随线程而灭,而堆内存和方法区是线程共享,需要动态回收内存

3.2对象已死吗?

  • 引用计数算法:
    给对象添加一个引用计数器,每当有一个地方引用它时,计数器+1,当引用失效时,计数器-1,任何时刻计数器为0的对象就是不可再使用的,微软的com、使用ActionScript 3的FlashPlayer、Python,但是存在对象互相引用无法回收的问题.

  • 可达性分析算法:
    当一个对象到GC Roots没有任何引用时,则证明对象时不可用的

  • GC Roots包括:
    虚拟机栈(栈中本地变量表)引用的对象
    方法区类静态属性引用的对象
    方法区中常量引用的对象
    JNI引用的对象

  • 引用类型:
    强引用
    弱引用
    软引用
    虚引用

  • 回收方法区(永久代),主要包括两部分内容:
    1)废弃常量:与java堆对象回收类似,可回收常量池中的字面量、他类(接口)、方法、字段的符号引用
    2)无用的类: 同时满足条件 该类所有的实例都被回收、加载该类的ClassLoader已经被回收、该类的对应的java.lang.Class对象没有任何地方被引用,无法再任何地方通过反射访问该类的方法。

3.3垃圾收集算法

3.3.1 标记-清除算法(基础)

不足:

  • 效率问题,标记和清除两个过程效率都不高
  • 空间问题,清除完成后空间碎片较多,如果需要分配大内存,不得不提前触发垃圾回收

3.3.2 复制算法

它将可用内存等量的两块,每次只使用其中一块,当这一块内存用完了,把还可用的对象复制到另外一块内存,然后把已使用过的内存直接清理掉
优点:

  • 实现简单,运行高效,内存连续
    不足:
  • 内存少了一半

目前都采用这种算法来收集 新生代内存 ,一般是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次都使用Eden空间和一块Survivor空间,当回收时将存活的对象复制到一块Survivor空间,清理使用的Eden和Survivor空间

3.3.3 标记-整理算法

标记过程仍然与 (标记-清除算法)一样,但是后续步骤不是直接对可回收对象进行整理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的对象内存

一般老年代因对象存活率高,没有额外空间做分配担保,就必须使用 标记-清除算法、标记-整理算法

3.4HotSpot的算法实现

3.4.1 枚举根节点

因为在要保持“一致性”,导致GC进行时必须停顿所有Java执行线程的一个重要原因,目前是使用一句称为OopMap的数据结构来存储这些信息供GC使用。

3.4.2 安全点

Java虚拟机并没有为每条指令生成OopMap,只是在 安全点 才暂停生成OopMap。

问题一:安全点的选定标准 是否具有让程序长时间执行的特征

  • 方法调用
  • 循环跳转
  • 异常跳转等

问题二:如何在GC发生时让所有线程都跑到最近的安全点上再停顿下来

  • 抢先中断式:就是让所有线程先停下,如果发现有的线程还没有到安全点,就恢复线程,继续执行到安全点,目前几乎都不采用此方法。
  • 主动中断式:不直接对线程操作,仅仅简单的设置标志位,各个线程执行时主动轮询这个标志,发现中断标志为真时就自己中断挂起.

3.4.3 安全区域

处理场景是线程处于Sleep或者Blocked状态,此时线程无法响应JVM的中断请求。此时需要安全区域来解决,在线程执行到安全区域中,首先标识自己进入了安全区域,当在这段时间内发生了GC时,就不用处理已经标识为安全区域的线程,在线程要离开安全区域时,它要检查是否完成了整个GC过程,如果完成就继续执行,如果没有则等待,直到收到可以离开安全区域的信号.

3.5 垃圾收集器

三. 垃圾收集器与内存分配策略_第1张图片
HotSpot的垃圾收集器

3.5.1 Serial收集器

  • 多用于Client模式下,新生代收集器,可配合CMS收集器使用
  • 单线程收集器,简单高效,“Stop The World”模式
  • 新生代采用复制算法

3.5.2 ParNew收集器

  • 多用于Server模式下,新生代收集器,可配合CMS收集器使用
  • 多线程收集器,“Stop The World”模式
  • 新生代采用复制算法

3.5.3 Parallel Scavenge收集器

  • 新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU用于运行用户代码的时间/CPU总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。
  • Parallel Scavenge收集器提供了两个参数用于精准控制吞吐量:
    a.-XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,是一个大于0的毫秒数。
    b.-XX:GCTimeRation:直接设置吞吐量大小,是一个大于0小于100的整数,也就是程序运行时间占总时间的比率,默认值是99,即垃圾收集运行最大1%(1/(1+99))的垃圾收集时间。
  • Parallel Scavenge是吞吐量优先的垃圾收集器,它还提供一个参数:-XX:+UseAdaptiveSizePolicy,这是个开关参数,打开之后就不需要手动指定新生代大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、新生代晋升年老代对象年龄(-XX:PretenureSizeThreshold)等细节参数,虚拟机会根据当前系统运行情况收集性能监控信息,动态调整这些参数以达到最大吞吐量,这种方式称为GC自适应调节策略,自适应调节策略也是ParallelScavenge收集器与ParNew收集器的一个重要区别。

3.5.4 Serial Old收集器

  • Serial Old是Serial垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的java虚拟机默认的年老代垃圾收集器。
  • 在Server模式下,主要有两个用途:
    a.在JDK1.5之前版本中与新生代的Parallel Scavenge收集器搭配使用。
    b.作为年老代中使用CMS收集器的后备垃圾收集方案。

3.5.5 Parallel Old收集器

  • Parallel Old收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在JDK1.6才开始提供。
  • 在JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配年老代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和年老代Parallel Old收集器的搭配策略。

3.5.6 CMS收集器

  • CMS(Concurrent Mark Sweep)收集器是一种以 获取最短回收停顿时间为目标 的收集器。CMS收集器是HotSpot虚拟机中的一款真正意义上的并发收集器,第一次实现了让垃圾回收线程和用户线程(基本上)同时工作。用CMS收集老年代的时候,新生代只能选择Serial或者ParNew收集器。
  • CMS工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下4个阶段:
    a.初始标记:只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
    b.并发标记:进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
    c.重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
    d.并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。
    由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行。
    CMS收集器工作过程:


  • CMS收集器有以下三个不足:
    a.CMS收集器对CPU资源非常敏感,其默认启动的收集线程数=(CPU数量+3)/4,在用户程序本来CPU负荷已经比较高的情况下,如果还要分出CPU资源用来运行垃圾收集器线程,会使得CPU负载加重。
    b.CMS无法处理浮动垃圾(Floating Garbage),可能会导致Concurrent ModeFailure失败而导致另一次Full GC。由于CMS收集器和用户线程并发运行,因此在收集过程中不断有新的垃圾产生,这些垃圾出现在标记过程之后,CMS无法在本次收集中处理掉它们,只好等待下一次GC时再将其清理掉,这些垃圾就称为浮动垃圾。
    CMS垃圾收集器不能像其他垃圾收集器那样等待年老代机会完全被填满之后再进行收集,需要预留一部分空间供并发收集时的使用,可以通过参数-XX:CMSInitiatingOccupancyFraction来设置年老代空间达到多少的百分比时触发CMS进行垃圾收集,默认是68%。
    如果在CMS运行期间,预留的内存无法满足程序需要,就会出现一次ConcurrentMode Failure失败,此时虚拟机将启动预备方案,使用Serial Old收集器重新进行年老代垃圾回收。
    c.CMS收集器是基于标记-清除算法,因此不可避免会产生大量不连续的内存碎片,如果无法找到一块足够大的连续内存存放对象时,将会触发因此Full GC。CMS提供一个开关参数-XX:+UseCMSCompactAtFullCollection,用于指定在Full GC之后进行内存整理,内存整理会使得垃圾收集停顿时间变长,CMS提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,用于设置在执行多少次不压缩的Full GC之后,跟着再来一次内存整理。

3.5.7 G1收集器

G1是一款面向服务器应用的垃圾收集器

  • 并行与并发
  • 分代收集
  • 空间整合
  • 可预测的停顿

3.6 内存分配与回收策略

Java自动内存管理主要解决2个问题:分配内存回收内存

  • 一般情况,对象在新生代Eden区中分配,当Eden区中没有足够空间分配时,虚拟机将发生一次Minor GC,如果发现对象全部无法放入Survivor空间,只好通过分配担保机制放转移到老年代

  • 大对象直接进入老年代: 是指需要大量连续内存空间的Java对象,最典型的大对象就是很长的字符串或数组

  • 长期存活的对象将进入老年代: 虚拟机给每个对象定义了一个对象年龄(age)计数器。计算方式是如果对象在Eden出生并经过一次Minor GC后仍然存活,并且可被Survivor区容纳,将被移动到Survivor空间中,对象年龄为1,此后对象每熬过一次Minor GC,年龄加1岁,当增加到15岁(默认),就将晋升到老年代。

  • 动态对象年龄判断: 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一般,年龄大于或等于该年龄的对象就可以直接进入老年代。

  • 空间分配担保

你可能感兴趣的:(三. 垃圾收集器与内存分配策略)