Java进阶系列:GC分析与优化

本文主要介绍以下内容:

  • 理论
    • JVM内存模型
      • 内存结构
      • 内存模型
    • 判定需要被回收的对象
    • 方法区回收策略
    • 垃圾回收算法
  • 实践
    • 查看
    • 优化

JVM内存模型

  • 内存结构
    Java进阶系列:GC分析与优化_第1张图片
  • Java内存模型
    • CPU缓存和内存的关系
      • 保证其它CPU的写入动作对该CPU是可见的,而且该CPU的写入动作对其它CPU也是可见的
        • 禁止指令的重排序
        • 内存屏障
          • 阻止屏障两侧指令重排序
          • 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
        • 缓存行失效
        • happens-before关系
      • 具体实现:volatile、final和synchronized
        • synchronized:
          • 在一个线程退出同步块时,线程释放monitor对象,它的作用是把CPU缓存数据(本地缓存数据)刷新到主内存中,从而实现该线程的行为可以被其它线程看到
          • 在其它线程进入到该代码块时,需要获得monitor对象,它在作用是使CPU缓存失效,从而使变量从主内存中重新加载,然后就可以看到之前线程对该变量的修改。
        • final
          • 正确的构造一个对象后,final字段被设置后对于其它线程是可见的
        • volatile
          • volatile的内存语义和sychronize获取和释放monitor的实现目的是差不多的
          • volatile禁止它之前和之后的指令写入行为的重排序
  • 参考博客:
    • 什么是Java内存模型
    • 深入理解JVM-内存模型(jmm)和GC

判定需要被回收的对象

  • 引用计数法
    • 对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加一;当引用失效,计数器值减一;任何时刻计数器值都为零的对象就是不可能再被使用了
    • 需要考虑很多额外情况
    • 很难解决对象之间相互循环引用的问题
  • 可达性分析算法
    • 通过一系列称为 GC Roots 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程走过的路径称为引用链。如果某个对象到 GC Roots 间没有任何引用链相连,则证明此对象是不可能再被使用,可以回收
    • GC Roots 的对象包括:
      • 在虚拟机栈(栈帧中的本地变量表)中引用的对象
      • 方法区中类静态属性引用的对象
      • 方法区中常量引用的对象
      • 本地方法栈中 JNI(即通常所说的 Native 方法)引用的对象
      • Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(NullPointException、OutOfMemoryError)
      • 所有被同步锁持有的对象
      • 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等

方法区回收策略

  • 方法区的垃圾收集主要回收两部分:废弃的常量和不再使用的类型
  • 判定一个常量是否废弃相对简单,与对象类似,只要某个常量不再被引用,就会被清理。
  • 判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了,需要同时满足下面三个条件:
    • 该类的所有实例都已经被回收,即 Java 堆中不存在该类及其任何派生子类的实例
    • 加载该类的类加载器已经被回收
    • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾回收算法

  • “Minor GC”、“Major GC”、“Full GC”

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

    • “标记-复制”算法:
      • 新生代划分为一块较大的 Eden 区和两块较小的 Survivor 区,每次分配只使用 Eden 区和其中一块 Survivor 区。发生垃圾收集时,将 Eden 区和 Survivor 区中仍然存活的对象一次性复制到另一个 Survivor 区,然后直接清理掉 Eden 区和已经用过的 Survivor 区
      • 当 Survivor 空间不足以容纳一次 Minor GC 之后存活的对象时,上一次新生代存活下来的对象直接进入老年代
      • 标记 - 复制算法不适合用在对象存活率高的区域,而且会浪费一半的空间
    • “标记-清除”算法
      • 先标记,再回收
      • 执行效率不稳定、内存空间碎片化
    • “标记-整理”算法
      • 老年代中让所有存活对象都向内存空间的一侧移动,然后直接清理掉边界以外的内存
      • 移动存活对象并更新其引用将会是一个极为繁重的操作,必须暂停用户应用程序线程才能进行
  • 跨代引用

  • 参考博客:Java 虚拟机垃圾收集机制详解

你可能感兴趣的:(高并发,高可用,高性能专题,jvm,垃圾回收,java)