JVM基础之垃圾回收器和内存分配(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/duanxl11234/article/details/88763736

“合抱之木,生于毫末;千里之行,始于足下;九层之台,起于垒土。”

一、堆内对象引用分析

  1. 引用计数法
  • 引用计数法(Reference Counting)的实现简单,判定效率高,目前是python使用的GC算法。
  • 其为对象添加一个引用计数器,每当有一次引用,计数器就加1;引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能再被使用的。
  • 由于它很难解决对象之间互相循环引用的问题,因此没有被主流的Java虚拟机选用。
  1. 可达性分析算法
  • 以“GC Roots”对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。
  • 当一个对象到GC Roots没有任何引用链时,则证明该对象是不可用的。
  1. 引用的类型
  • 强引用(Strong Reference)只要还存活,垃圾收集器就永远不会回收。
  • 软引用(Soft Reference)关联的对象,在系统将要发生内存溢出之前,将会把这些对象列进回收范围进行第二次回收,如果第二次回收后还是没有足够的内存,才会抛出OOM异常。
  • 弱引用(Weak Reference)关联的对象,在垃圾收集器工作时,无论当前内存是否足够,都一定会被回收。
  • 虚引用(Phantom Reference)只是了为了关联的对象在被垃圾收集器回收时收到系统通知。
  1. finalize()方法
  • 当对象被垃圾收集器第一次标记时执行该方法,将对象放入F-Queue队列,并有虚拟机建立的低优先级的Finalizer线程执行,对象进行“自救”。稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象已经被标记,则进行回收。
  • finalize()方法在对象的生命周期内只会执行一次。
  • 不推荐使用该方法。

二、对象回收机制

  1. 标记-清除
  • 分为“标记”和“清除”两个阶段,是最基础的收集算法。
  • 不足之处有两个,一是效率问题,二是会造成内存空间产生大量不连续的内存碎片。
  1. 标记-整理
  • 分为“标记”和“整理”两个阶段,主要适用于老年代中进行内存回收。
  • 首先标记对象,然后让所有存活对象向一端移动,然后直接清理边界以外的内存。
  1. 复制
  • 为了解决效率问题,提出了“复制”收集算法,它将可用内存划分为大小相等的两块,每次使用其中一块。
  • 两块内存中一块用完时,进行标记,将存活的对象复制到另一块中,并将已使用过得内存空间清理。
  • 缺点是会浪费一半的内存。根据研究发现,98%的对象都是朝生夕死,所以又进一步划分为新生代Eden和Survivor空间,比例为8:1,将新生代中存活的对象复制到Survivor空间中,当Survivor空间不足时,依赖老年代进行分配担保。
  1. 分代收集
  • 当今商业虚拟机的垃圾回收机制,根据对象存活周期的不同将内存划分为几块,再分别选择适合的收集算法对各个分块进行回收。
  • Java中新生代使用“复制算法”,老年代使用“标记-清楚”或“标记-整理”。

三、垃圾收集器

  1. Serial
  • 最基本、发展历史最悠久的收集器,在新生代工作,是一个单线程的收集器,在进行垃圾收集时,必须暂停其他所有的工作现场,直到收集结束。
  1. ParNew
  • 是Serial收集器的多线程版本,在新生代工作,收集算法、Stop The World、对象分配规则、回收策略等都与Serial完全一样。
  • 它是Server模式下的虚拟机中首选的收集器,目前能与CMS配合的新生代收集器只有它。
  1. Parallel Scavenge
  • Parallel Scavenge收集器是新生代收集器,使用复制算法,是并行的多线程收集器,又称“吞吐量优先”收集器。
  • 它关注的目标是达到一个可控制的吞吐量(Throughput,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),如虚拟机总运行时间100分钟,其中垃圾收集时间1分钟,那么吞吐量就是99%)。
  • -XX:MaxGCPauseMillis控制最大停顿时间,-XX:GCTimeRatio设置吞吐量的大小。不要认为把停顿时间设置的很小会使垃圾收集速度变快,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的。
  1. Serial Old
  • 它是Serial收集器的老年代版本,同样是一个单线程收集器,使用标记-整理算法,主要是作为Client模式下的虚拟机使用。
  • 当CMS发生Concurrent Mode Failure时触发它来进行FullGC。
  1. Parallel Old
  • 它是Paralled Scavenge收集器的老年代版本,使用多线程和标记-整理算法。
  • 与Paralled Scavenge配合,实现真正的“吞吐量优先”收集器。
  1. CMS收集器
  • CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,主要应用在互联网站的服务端上,尤其重视服务的响应速度和系统停顿时间最短,基于“标记-清除”算法。
  • 由四个步骤完成回收:初试标记、并发标记、重新标记、并发标记。
  • 有三个明显的缺点,一是对CPU资源敏感,会因为分配运算能力去执行收集器线程而导致用户程序执行速度降低;二是无法处理“浮动垃圾(Floating Garbage)”,可能会出现“Concurrent Mode Failure”失败导致Full GC发生;三是因为基于“标记-清除”算法,所以会导致大量的空间碎片,为解决这个问题,CMS提供了-XX:+UseCMSCompactAtFullCollection,用于在CMS收集器顶不住要进行FullGC时开启内存碎片合并整理过程,还提供了-XX:CMSFullGCsBeforeComaction设置执行多少次不压缩FullGC后进行一次带压缩的FullGC。
  1. G1收集器
  • G1(Garbage First)收集器是当今收集器技术发展的最前沿成果之一,由2004年Sun实验室发表的论文提出,是一款面向服务端应用的垃圾收集器,HotSpot开发团队赋予它的使命是替换掉CMS收集器。
  • 并行与并发:充分利用多个CPU和多核来缩短Stop-The-world停顿时间,并且可以在GC动作时保证Java程序继续执行。
  • 分代收集:分代概念在G1中依旧存在,G1不需要其他收集器配合就能够独立管理整个GC堆。
  • 空间整合:G1从整体看是基于“标记-整理”算法实现收集器,从局部看是基于“复制”算法实现。它将堆空间划分为多个大小相同的独立区域(Region),依旧保留新生代和老年代的概念,但两者不再是物理隔离的,它们都是一部分Region的集合。
  • 可预测的停顿:G1除了追求低停顿,还建立可预测的停顿时间模型。

四、内存分配与回收策略

  1. 新生代策略
  • 大多数情况下,对象在新生代Eden区分配(大对象会直接分配到老年代),当Eden区没有足够内存时,发起一次MinorGC。
  1. 老年代策略
  • 较大的对象分配到老年代,我们应当尽量避免短生命的大对象产生,通过-XX:PretenureSizeThreshold参数可以设置直接进入老年代大对象的阈值。
  • 长期存活的对象将进入老年代,虚拟机为每个对象定义一个对象年龄计数器。对象在Eden出生并经历第一次MinorGC后仍然存活,并且能被Survivor容纳,就将对象移动到Survivor中,并且设对象年龄为1,对象每次在Survivor区存活过一次MinorGC,年龄就加1,当年龄增长到一定程度(默认为15)就会晋升到老年代中,晋升老年代的阈值可以通过-XX:MaxTenuringThreshold设置。
  • 动态对象年龄判定,当Survivor空间中相同年龄的对象超过空间的一半,则大于等于该年龄的对象都直接进入老年代,无需等待到达MaxTenuringThreshold阈值。
  • 空间分配担保,在MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立则MinorGC是可以确保安全的。如果不成立,查看HandlePromotionFailure设置值是否允许担保失败。如果允许,检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小。如果大于就尝试一次MinorGC,如果小于或者不允许担保失败,则要进行一次FullGC。如果尝试MinorGC失败,则进行一次FullGC,通常HandlePromotionFailure被打开,为了避免频繁进行FullGC。JDK1.6之后HandlePromotionFailure被废弃,只要老年代的连续最大空间大于新生代对象总大小或者历次晋升老年代对象的平均大小就进行MinorGC,否则进行FullGC。

说明

  • 文中内容主要来自于《深入理解Java虚拟机》,更多内容请关注原著。
  • 此文是读书时对重点知识的记录,如有错误请一定指出。

猜你喜欢

转载自blog.csdn.net/duanxl11234/article/details/88763736