java 垃圾收集器

参考《深入理解Java虚拟机》

为什么要去了解GC和内存分配?

当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集器成为系统达到更高并发量的瓶颈时,就需要对这些”自动化”的技术实施必要的监控和调节

可作为GC Roots的对象包括下面几种

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

java对引用的概念

  • 强引用:类似Object obj = new Object()这类引用,只要强引用还存在,GC就永远不会回收被引用的对象
  • 软引用:描述一些还有用但并非必须的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足有的内存,才会抛出内存溢出异常
  • 弱引用:也是用来描述非必须对象,强度比软引用更弱一些。弱引用关联的对象只能生存到下一次GC发生之前。
  • 虚引用:最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。为对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知

判断对象是否真正死亡

即使判断为不可达对象,也并非”非死不可”。要真正宣告一个对象死亡,至少要经历两次标记过程:

  • 1.在可达性分析后,发现没有与GC Roots相连接的引用链,那它将会被第一次标记并进行一次筛选
  • 2.判断此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或finalize()方法已被JVM调用过,则JVM都判断为”没有必要执行”
  • 如果判定为有必要执行finalize()方法,这个对象会被放在一个F-Queue队列,由低优先级的Finalizer线程中去执行
  • 稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象在finalize()中重新与引用链上的任何一个对象建立关联,那它就会被移出”即将回收”的集合。否则真正被回收
  • 任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,将不再执行finalize()方法
  • finalize()作用:有时当撤销一个对象时,需要完成一些操作。例如:一个对象正在处理的是非Java资源,如文件句柄或Windows字符字体,这时要确认在一个对象被撤销前保证这些资源被释放,为处理这样的状况,Java提供了被称为收尾(finalization)的机制。使用该机制可以定义一些特殊的操作,这些操作在一个对象将要被垃圾回收程序释放前执行

《thinking java》:java提供finalize()方法,GC准备释放内存时,会先调用finalize()。

  • 对象不一定会被回收
  • 垃圾回收不是析构函数
  • 垃圾回收不与内存有关
  • 垃圾回收和finalize()都靠不住,只要JVM还没有快到耗尽内存的地步,是不会浪费时间执行垃圾回收的

注:finalized是Java刚诞生时,为了使C/C++程序员更容易接受它所做出的一个妥协,它的运行代价高昂、不确定性大,无法保证各个对象的调用顺序。所有finalized()能做的工作,使用try-finally或者其他方式都可以做的更好

回收方法区(永生代)

  • Java虚拟机规范中说过可以不要求JVM在方法区实现垃圾收集
  • 永生代的垃圾收集主要回收两部分内容:废弃常量和无用的类
    • 回收字面量常量:没有任何String对象引用常量池中的”abc”常量,也没有其他地方引用了这个字面量
      • 常量池中的其他类(接口)、方法、字段的符号引用也与此类似
    • 判断无用的类:
      • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例
      • 加载该类的ClassLoader已经被回收
      • 该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法
  • JVM**可以**对满足上述3个条件的无用类进行回收,然后不是必然被回收。可以通过调参控制

垃圾收集算法

java中新生代采用复制算法回收:

  • 98%对象都是”朝生暮死”,HotSpot虚拟机采用默认Eden和两块Survivor,大小为8:1:1
  • 当survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保(Handle Promotion)

老年代采用标记-整理算法

  • 让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存

HotSpot算法实现

  • GC必须在一个能确保一致性的快照中进行——”一致性”是指在整个分析期间,整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中对象引用关系还在不断变化的情况
  • 这点是导致GC进行时必须停顿所有Java执行线程的一个重要原因

安全点

  • 在HotSpot的实现中,使用一组称为OopMap的数据结构来得知哪些地方存放着对象引用
  • 实际上OopSpot没有为每条指令都生成OopMap,只在被称为”安全点(Safepoint)”的特定位置记录这些信息
  • 执行程序时,并非在所有地方都能停下来开始GC,只有在到达安全点时才能暂停
  • 如何在GC发生时让所有线程都”跑”到最近的安全点上再停顿下来,有两种方案:
    • 抢先式中断当GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它”跑”到安全点上。现在几乎没有虚拟机采用这种方式
    • 主动式中断:当GC需要中断线程时,不直接对线程操作,仅简单设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时,就自己中断挂起。轮询标志的地方和安全点是重合的。

安全区域

  • 当线程”不执行”时,就是没有分配CPU时间片,比如线程处于Sleep\Blocked状态,这时线程无法响应JVM的中断请求,并运行到安全的地方去中断挂起,这种情况需要安全区域(Safe Region)来解决
  • 安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域的任意地方开始GC都是安全的。Safe Region可以看做是被扩展的Safepoint

垃圾收集器

并行和并发

  • 并行(Parallel):指多条垃圾收集器线程并行工作,但此时用户线程仍处于等待状态
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行

Serial(串行垃圾回收器)

  • 它在进行垃圾收集时,必须暂停其他所有的工作线程
  • 简单、高效,对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,可以获得最高的单线程效率
  • 适合简单的命令行程序

ParNew(并行垃圾回收器)

  • 是Serial收集器的多线程版本
  • 是JVM默认的新生代收集器
  • 在单CPU下,效果没有Serial好,但多CPU时效果好

Parallel Scavenge(吞吐量优先)

  • 同ParNew类似,关注点是尽可能地缩短垃圾收集时用户线程的停段时间

CMS(Cuncurrent Mark Sweep)(并发编辑扫描垃圾回收器)

  • 以获取最短回收停顿时间为目标的收集器
  • 以”标记——清算”算法实现的,分为四个步骤
    • 初始标记:仅仅只是标记一下GC Roots能直接关联到的对象
    • 并发标记:进行GC Roots Tracing的过程
    • 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动
    • 并发清除
  • 初始标记和重新标记这两步,仍要”Stop The World”
  • 整个过程耗时最长的并发标记和并发清除过程,收集器线程都可以与用户线程一起工作

  • 有如下缺点:
    • CMS收集器对CPU资源非常敏感,会占用一部分线程(CPU资源)导致应用程序变慢,总吞吐量会降低
    • CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现失败而导致另一次Full GC的产生。因为在并发清理阶段用户线程还在运行着,伴随程序就会有新的垃圾产生
    • 容易产生内存碎片

G1收集器

G1是一款面向服务器端应用的垃圾收集器,未来可以替换JDK1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点:

  • 并行与并发:G1充分利用多CPU、多核环境,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行
  • 分代收集:G1可以不需要其他收集器配合就能独立管理整个GC堆。采用不同方式去处理新创建的对象和已经存活一段时间的旧对象
  • 空间整合:G1基于”标记-整理”算法,在G1运作期间不会产生内存空间碎片
  • 可预测的停顿:G1C除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒

使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合

G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(Garbage-First名称的由来)

G1收集器的运作大致可划分为以下几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

猜你喜欢

转载自blog.csdn.net/qq_23348071/article/details/78404648