Java JVM

194 阅读8分钟

Java 编译解释型语言

image.png

Java 解释过程

image.png

Java 字节码

image.png

JVM结构

JVM结构

image.png

类加载子系统

image.png

ClassLoader 双亲委派

image.png

BootStrapClassLoader 对应目录 jre/lib

ExtClassLoader 对应目录 jre/lib/ext

AppClassLoader 对应目录 classpath

String.class.getClassLoader(); 获取加载类的加载器; 如果为空则表示是BootStrapClassLoader

运行时数据区

运行时数据区

运行时数据区

PC Register Program Counter

当前执行程序的地址

java方法栈

虚拟机栈

  • 一个线程对应一个虚拟机栈,

  • 方法开始执行进栈,执行完出站,

  • StackOutFlowError 栈溢出,方法调用层数太多

  • OutOfMemoryError 内存溢出,线程太多,内存不够创建虚拟机栈

  • -Xss设置栈大小;

栈帧

栈帧.png

  • 一个栈帧对应一个方法

堆区

堆区

JVM规范中规定所有的对象和数组都应该存放在堆中,在执行字节码指令时,会把创建的对象存放在堆中,对象对应的引用地址存放在虚拟机栈中的栈帧中,不过方法执行完之后,刚刚所创建的对象并不会立马被回收,而是要等JVM后台执行GC后,对象才会被回收。

  • Xms:ms(memory start) 指定堆的初始化内存大小,等价于-XX:InitalHeapSize

  • Xmx:mx(memory max) 指定堆的最大内存大小,等价于-XX:MaxHeapSize

  • 默认情况下,初始化内存大小 = 物理内存大小/64,最大内存大小 = 物理内存大小/4;

  • 一般会把-Xms 和 -Xmx设置为一样,这样 JVM 就不需要再 GC 后去修改堆的内存大小了,提高效率;

  • -XX:NewRatio 参数设置新生代和老年代的比例,默认是2,新生代占比1,老年代占2,也就是新生代占堆内存总大小1/3

  • 新生代:Eden区、S0区、S1区的比例是8:1:1,-XX:SurvivorRatio

堆区对象分布
  1. 新创建对象放在 Eden 区,
  2. Eden 区满了之后进行 Young GC 之后, 把剩下的对象放入到 S0 区,并标记1
  3. Eden 区满了之后 S0 Young GC 之后,把 S0 区剩下的对象放入 S1 区,并标记2
  4. 重复 2 3 直到标记数达到 15 之后,还存活的对象放入到老年代

GC垃圾回收器

image.png

  • Young GC :负责对新生代进行垃圾回收

  • Old GC :对老年代进行垃圾回收,(目前CMS垃圾回收会单独对老年代进行垃圾回收,其他垃圾回收器基本都是整个堆回收才会回收老年代)

  • Full GC :对整个堆进行垃圾回收,包括新生代、老年代

  • 新生代存放刚创建的对象;

  • 老年代存放一些存活时间比较久的对象;

  • 新生代首先进入Eden区,Young GC 之后存活对象会从 Eden 区放到S0区,之后GC 会从S0 放到S1区,之后跳转到S0区,来回跳转15次之后,再GC 就会进去老年代;

常见的垃圾收集器

image.png

-XX:+UseParallelGC

垃圾标记阶段

引用计数法

每个对象都保持一个引用计数器属性,用户记录对象被引用的次数

优点:

  • 实现简单,计数器为0 ,表示垃圾对象

缺点:

  • 需要额外的空间来存储计数,
  • 需要额外的时间来维护引用计数
  • 相互引用,无法处理的问题

image.png

可达性分析法(用的最多的)

以GC Roots 作为起始点,然一层层找到所引用的对象,被找到的对象就是存活对象,那么不可达对象就是垃圾对象

GC Root 的对象有哪些:(重点前面4种)

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象,各个线程调用方法堆栈中使用到的参数、局部变量、临时变量等

  • 方法区中的类的静态属性引用的对象:java类的引用类型静态变量

  • 方法区中常量引用的对象:字符串常量池的引用

  • 本地方法中 jni(native方法) 引用的对象;

  • JVM内部引用的对象(class对象、异常对象NullPointException、OutOfMemoryError、类加载器)

  • 所有被同步锁(syncchronized关键)持有的对象

  • JVM内部的JMCBean、JVMTI中注册的回调、本地代码缓存等

  • JVM实现中的”临时性“对象,跨代引用的对象;

image.png

GC:垃圾回收阶段

标记-清除

一种非常基础和常用的垃圾回收算法, 针对某块内存空间,比如新生代、老年代,如果可用内存不足后,就会STW,暂定用户线程执行,执行垃圾回收操作

  1. 标记阶段:从GC Roots开始遍历,找到可达对象,并在对象头中进行记录
  2. 清除阶段:堆 内存空间进行线性遍历,如果发现对象头中没有记录可达对象,则回收它

缺点:

  • 效率不高,
  • 空间碎片

image.png

标记-复制

利用空间换时间,需要把内存空间分为两块,每次使用一块,在进行垃圾回收时,将可达对象复制到另外没有被使用的内存块中,然后在清除当前内存块中的所有对象,后续再按照同样的流程进行垃圾回收;

可达对象比较多就不适合,需要复制量就比较大

image.png

优点 :

  • 新生代适合标记复制

标记-整理

  1. 第一阶段:通过 GC Root 找到标记可达对象,
  2. 第二阶段:将所有存活的对象移动到内存的一端,整体清除

image.png

总结

image.png

分代垃圾回收算法

  • 新生代中的对象存活时间比较短,那么就可以利用复制算法,它适合垃圾对象比较多的情况
  • 老年代中的对象存活时间比较长,所以不太适合用复制算法,可以使用 标记-清除 、标记-整理算法,比如 CMS 使用标记-清除算法, Serial Old 垃圾收集器使用标记-整理

CMS 并行标记-清除算法:Concurrent Mark Sweep

image.png

CMS 整个垃圾回收过程变长,但是用户暂停时间STW变短,而且在垃圾回收过程中大部分时间用户线程还在执行,用户体验比较好,吞吐量变低了(单位时间内执行的用户线程变少了,被CMS GC 占用)

初始标记

  • STW ,暂停所有用户线程 STW: Stop The World
  • 然后标记出 GC Roots 可达对象
  • 一旦标记完成,恢复所有工作线程继续执行
  • 这个时间比较短

并发标记

  • 并发执行,不会 STW,用户线程和 GC 线程同步执行
  • 从上一个阶段标记出来的对象,开始遍历整个老年代,标记出所有的可达对象
  • 比较耗时
  • 并发执行,可能出现误差,所以按三色标记

重新标记

  • 对上一个阶段标记的对象,进行修正,上一阶段并行
  • 需要STW ,但是时间不会很长
  • 增量更新

并发删除

  • 删除标记的对象,不需要STW
  • 由于不需要移动对象,所以和工作线程并发执行,

并发重置

CMS总结

如果在并发标记、并发清理的过程中,由于用户线程同时执行,如果有新对象进入老年代,但是空间又不够,就会造成”concurrent mode failure“,此时就会利用Serial Old来做一次垃圾收集,就会做一次全局的STW,

在并发清理的过程中,产生新的垃圾,称为”浮动垃圾“,只能等下一次 GC 的时候来清理;

由于使用标记-清除算法,所以会产生内存碎片,可以通过参数-XX:+UseCMSCompactAtFullCollection 让JVM 执行完标记-清除 之后再执行一次整理;

也可以通过-XX:CMSFullGCsBeforeCompaction 来指定多少次 GC 之后执行整理,默认是0, 每次GC之后都会整理;

G1 (Garbage First)

Garbage First

每一个方块叫Region,堆内存会分为2048个Region,每个Region的大小等于堆内存/2048;

还是分了Eden区、S0区、S1区、老年代,不过空间不是连续的

Humongous 区是专门用来存放大对象的,(如果一个对象的大小超过一个Region的50%,那么就是大对象)

image.png

筛选回收

  • 需要STW,来清除垃圾对象,
  • 可以通过-XX:MaxGCPauseMillis 来指定GC STW 停顿的时间,所以可能不会回收掉所有垃圾对象,默认是200ms
  • 采用的复制算法,不会产生碎片(会把某个 Region 里的垃圾对象复制到另外空间 Region 区域,比如相邻的)
  • YoungGC :Eden区满,就会触发G1的YoungGC ,对Eden区进行GC
  • MixedGC :老年代的占用率达到了-XX:InitiatingHeapOccupancyPercnet 指定百分比,回收所有的新生代以及部分老年代,以及大对象区;
  • FullGC : 在进行MixedGC过程中,采用的复制算法,如果复制过程中内存不够,则会触发FullGC ,会STW,并采用单线程来进行标记-整理算法进行GC,相当于用一次Serial GC