JVM

347 阅读8分钟

1 JVM 运行时数据区

1.1 Program Counter Register

当前线程所执行字节码的行号指示器

不会出现 OutOfMemoryError

1.2 VM Stack

  • 线程私有,生命周期和线程同
  • 方法执行时,同步创建一个栈帧

局部变量表,操作数栈,动态连接,方法出口

  • 局部变量表

存储基本数据类型,对象引用,returnAddress
局部变量表最小单位:slot
long 和 double 占用 2个 slot

异常情况

  • StackOverflowError:超出JVM设置栈深度
  • OutOfMemoryError:栈扩展时无法申请到足够的内存 常用参数
  • -Xss 线程栈空间大小

1.3 Native Method Stack

JVM 执行 native 方法时使用

异常情况

  • StackOverflowError:超出JVM设置栈深度
  • OutOfMemoryError:栈扩展时无法申请到足够的内存

1.4 Java 堆

异常情况

  • OutOfMemoryError:堆无法扩展时

出现原因

不断地创建对象, 并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象

// 堆异常信息
java.lang.OutOfMemoryError: Java heap space

处理思路

  1. 明确是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)
  2. Memory Leak

通过工具查看泄漏对象到GC Roots的引用链
找到泄漏对象是通过怎样的引用路径、 与哪些GC Roots相关联
根据泄漏信息及GC Roots引用链信息,定位到存在泄漏代码的具体位置

常用参数

  • -Xms 初始堆大小
  • -Xmx 最大堆大小
  • -Xmn 年轻代空间大小
  • -XX:SurvivorRatio 设置新生代中Eden和一个Survivor 空间比
  • -XX:PretenureSizeThreshold 对象大于设定值直接在老年代分配(Serial,ParNew 有效)
  • -XX:MaxTenuringThreshold 设置进入老年代的年龄阈值(最大值15)
  1. Memory Overflow

检查 JVM 堆参数,是否还有上调空间
从代码上检查是否有对象生命周期过长,持有状态时间过长,存储结构设计不合理等情况

1.5 Method Area

存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据 异常情况

  • OutOfMemoryError:方法区内存不足时

出现原因

运行时产 生大量的类去填满方法区, 直到溢出为止。

java.lang.OutOfMemoryError: Metaspace
	at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:348)
    
java.lang.OutOfMemoryError: GC overhead limit exceeded
	at org.objectweb.asm.MethodWriter.visitLabel(MethodWriter.java:1239)

处理思路

增加元空间大小

常用参数

  • -XX:MetaspaceSize 初始元空间
  • -XX:MaxMetaspaceSize 最大元空间
  • -XX:MinMetaspaceFreeRatio 在垃圾收集之后控制最小的元空间剩余容量的百分比
  • -XX:MaxMetaspaceFreeRatio 控制元空间剩余最大容量百分比

1.6 运行时常量池

方法区的一部分

异常情况

  • OutOfMemoryError:存储常量到常量池,当内存不足时

出现原因

存储常量到常量池,当内存不足时

// 堆异常信息
java.lang.OutOfMemoryError: Java heap space

1.7 直接内存

NIO 使用 native 函数直接分配的堆外内存

异常情况

  • OutOfMemoryError:扩展时无法申请到足够的内存
Exception in thread "main" java.lang.OutOfMemoryError
	at sun.misc.Unsafe.allocateMemory(Native Method)
	at com.wzh.jvm.error.DirectMemoryOOM.main(DirectMemoryOOM.java:16)

常用参数

  • -XX:MaxDirectMemorySize

2 垃圾回收

2.1 可达性分析算法

以GC Roots为起始节点集,根据引用关系向下搜索,对象不可达,即可判断为垃圾

2.1.1 GC Roots

  • VM Stack 中引用的对象
  • Method Area 中类静态属性引用的对象,常量引用的对象
  • Native Method Stack 中 JNI 引用的对象
  • JVM 内部的引用(基本类型的class对象,异常对象,类加载器)
  • 被同步锁持有的对象
  • 反映 JVM 内部情况的 JMXBean,JVMTI中注册的回调,本地代码缓存

2.1.2 步骤

  1. 根节点枚举都必须暂停用户线程(STW)
  2. 查找引用链(可并发执行)

2.2 引用

强引用

  • 类似 Object o=new Object()
  • 无论任何关系下,只要强引用存在,GC就永远不会回收被引用的对象

软引用

  • SoftReference 描述一些还有用,但非必须的对象
  • 在系统将要发送溢出前,会把软引用对象进行二次回收
  • 如果回收后还没有足够内存,才会抛出内存溢出异常

弱引用

  • WeakReference 描述非必须对象
  • 弱引用关联的对象只能生存到下一次GC 发生为止

虚引用

  • PhantomReference 为了能在这个对象被收集器回收时收到一个系统通知

2.3 垃圾回收分类

  • Young GC:新生代的垃圾收集
  • Old GC:老年代的垃圾收集 (CMS)
  • Mixed GC:新生代和部分老年代的垃圾收集 (G1)
  • Full GC:Java堆+方法区的垃圾收集

2.4 垃圾回收算法

2.4.1 Mark-Sweep(标记-清除)

  • 多作用于新生代
  • CMS 基于标记清除,工作在老年代

引入问题:

  1. 执行效率不稳定
  2. 产生碎片化空间

2.4.2 Semispace Copying(标记-复制)

  • 多作用于新生代
  • Serial,ParNew,Parallel Scavenge

引入问题

  1. 存在空间浪费

2.4.3 Mark-Compact(标记-整理)

  • 多作用于老年代
  • Serial Old
  • Parallel Scavenge基于标记整理算法

2.5 垃圾回收算法细节

  1. 垃圾回收器在 GC Roots枚举时必须暂停用户线程(STW)

枚举期间根节点集合的对象引用关系不变
OopMap 用于枚举 GC Roots
OopMap 记录了栈上本地变量到堆上对象的引用关系

  1. JVM 在安全点的位置生成 OopMap

当垃圾回收时,设置标志位
各线程执行时主动轮询标志位(汇编指令 test %eax,0x160100)
标志位为真,线程在最近的安全点主动中断挂起

  1. 安全区域

线程Sleep、BLocked状态进入安全区
垃圾回收时不管在安全区域的线程
GC Roots枚举完通知线程,才可离开安全区

  1. Remembered Set(记忆集)

用于记录从非收集区域指向收集区域的指针集合的数据结构
用于解决分代收集中对象跨代引用
卡表时记忆集的一种实现
利用Write Barrier维护卡表的状态

2.6 垃圾收集器

2.6.1 Serial 收集器

  • 最基础,最老的垃圾收集器
  • 单线程收集器
  • 工作在新生代
  • 基于标记-复制算法实现
  • 适用于运行在客户端模式下的虚拟机

2.6.2 ParNew 收集器

  • 新生代支持多线程并行收集
  • 工作在新生代
  • 基于标记-复制算法实现
  • 可以与CMS 收集器配合使用
  • 适用于JDK7运行在服务端模式下的虚拟机

2.6.3 Parallel Scavenge 收集器

  • 新生代支持多线程并行收集
  • 工作在新生代
  • 基于标记-复制算法实现
  • 实现可控制吞吐量的回收算法 常用参数
  • -XX:+MaxGCPauseMillis 控制最大垃圾收集停顿时间(>0 毫秒数)
  • -XX:+GCTimeRatio 设置吞吐量大小
  • -XX:+UseAdaptiveSizePolicy 打开自适应调节策略

2.6.4 Serial Old 收集器

  • 最基础,最老的垃圾收集器x
  • 单线程收集器
  • 工作在老年代
  • 基于标记-整理算法实现
  • 适用于运行在客户端模式下的虚拟机
  • JDK5之前与 Parallel Scavenge 配合使用
  • 作为 CMS 发生失败时的后背方案(并发收集发生Concurrent Mode Failure使用)

2.6.5 Parallel Old 收集器

  • 支持多线程并发收集
  • 工作在老年代
  • 基于标记-整理算法实现

关注吞吐量

2.6.6 CMS 收集器

  • 以获取最短回收停顿时间为目标
  • 基于标记-清楚算法实现
  1. 初始标记:标记一下GC Roots 能直接关联到的对象
  2. 并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程
  3. 重新标记: 修正并发标记期间,标记产生变动的标记对象
  4. 并发清除:清楚标记阶段判断死亡的对象

缺陷

  • CMS 对处理器的资源非常敏感
  • CMS 产生浮动垃圾,可能出现 Concurrent Mode Failure,导致 STW的 FullGC
  • CMS 产生碎片空间,导致 STW的 FullGC

常用参数

  • -XX:CMSInitiatingOccupancyFraction 设置触发CMS的百分比
  • -XX:+UseCMSCompactAtFullCollection 提前促发FullGC
  • -XX:CMSFullGCsBeforeCompaction CMS执行指定次数不整理空间的FullGC之后,下一次进入FullGC时会提前整理碎片(默认0,每次都整理)

2.6.7 Garbage First收集器

  • 一款主要面向服务端的垃圾收集器
  • 基于Region的堆内存布局
  • 哪块内存存放的垃圾数量最大,优先回收哪块内存
  • 建立了可预测停顿时间模型
  • G1从整体看基于标记-整理算法(OldGC),从局部看基于标记-复制算法(YoungGC)
  • Region是单次回收的最小单元
  • Hunongous存储大对象区域

跨Region引用对象

  • 每个Region维护自己的记忆集(RSet)
  • 记录下其他Region指向自己的指针
  • Rest存储结构时类 哈希表

Key:其他Region起始地址
Value:集合,存储卡表的索引号

并发标记阶段出现的标记错误

  • G1 通过原始快照实现
  • Region中的一部分划分出来,使用两个TAMS指针标记
  • 并发回收是新分配的对象地址必须要在两个 TAMS 上

  1. 初始标记:标记一下GC Roots 能直接关联到的对象,修改TAMS指针的值
  2. 并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程
  3. 最终标记:修正并发标记期间,遗留的STAB记录
  4. 筛选回收:更新Region统计数据,根据期望停顿时间制定回收计划,把存活对象复制到空的Region中,清理掉整个Region

内存回收的速度 < 内存分配数据,G1 进入 Full GC,导致长时间的STW

常用参数

  • -XX:G1HeapRegionSize 设置每个Region的大小(1M~32M)
  • -XX:MaxGCPauseMillis 设置垃圾回收的停顿时间(100 ~ 300 毫秒)