JVM架构和GC命令总结
- 概述
- JVM架构体系
- 垃圾回收
- 对象存活性判断
- 垃圾回收算法
- 垃圾回收器(回收算法的具体实现)
- 类装载
- 启动类加载器
- 扩展类加载器
- 应用程序类加载器
- 自定义类加载器
- 执行引擎
- 解释器
- 即时编译器
- 运行时数据区
- 堆heap
- 方法区
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 垃圾回收
- 垃圾回收器开启命令
- Reference
1、概述
Java虚拟机Java virtual Machine(JVM)是物理机器的软件实现
Java编译器javac将源码文件.java编译成字节码文件.class,然后这个字节码文件.class被放到JVM中,转载并执行字节码文件.class。
JVM Architecture
2、JVM架构体系
2.1 垃圾回收(Garbage Collection)
负责回收堆内存heap中没有被使用的对象判断对象是否存活、可达性检测
- 引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1,当引用失效时,计数器值就减1。任何时刻计数器为0的对象就是不可能再被使用的
- GC ROOT:通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots 没有任何引用链相连接时,就证明此对象是不可用的
2.2 垃圾回收算法
1)标记清除算法(Mark-Sweep)
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
- 效率问题:标记清除都不是高效动作
- 空间问题:标记清除之后产生大量不连续的内幕才能碎片,碎片太多导致分配较大对象时无法找到足够的连续内存而不得不提前触发另一次GC
2)复制算法Copying
它将可用内存按容量划分为大小相等的两块,每次只是用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清除掉
- 可用内存缩小为原来的一半
- 每次收集时,所有的标记对象都要被拷贝,从而导致一些生命周期很长的对象被来回拷贝多次,消耗大量的时间(分代回收来解决这个问题)
3)分代回收算法(Generational Collecting)
根据对象存活周期的不同将内存划分为几块
- 新生代:内存分为一块较大的Eden控件和两块较小的Survivor空间。新建object在Eden,Eden满时触发GC,Eden中存活对象别移动到S0区,清空Eden;等Eden再满再GC,Eden和s0中的存活对象被复制到s1(复制算法保证了s1中来自Eden和s0两部分的存活对象占用连续的内存空间,避免了碎片化);清空Eden和s0。
下一轮的时候s0和s1交换角色,如此循环往复。
如果对象的复制次数达到15次,该对象就会被送到老年代 - 老年代:对象存活率高
- 垃圾回收器(垃圾回收算法的具体实现)
- 并行与并发
- 并行Parallel:指多条垃圾收集线程并行工作,但此时用户线程扔处于等待状态
- 并发Concurrent:指用户线程与垃圾收集线程同时执行,用户程序在继续运行,而垃圾收集程序运行在另一个CPU上
- 新生代Minor GC:新生代内存不大,回收速度比较快
- 老年代Major GC/Full GC:内存一般比较大,用于缓存大对象,回收速度约Minor GC的10倍以上
- 吞吐量Throughput:JVM总共运行了100分钟,其中垃圾回收机花掉1分钟,那吞吐量就是99%
- 停顿时间:垃圾回收器正在运行时,应用程序的暂停时间(for streaming pipeline)
4)回收类型
- NEW
- serial:单线程
- parNew:serial多线程版本
- Parallel Scavenge:复制算法,并行,优先保证吞吐量,不顾及用户STW感受,吞吐量方面优化的系统,停顿时间长是可以接收的
- Old/tenured
- Serial Old:serial回收器的老年代版本,单线程,标记-整理算法
- Parallel Old:Parallel Scavenge回收器的老年代版本,多线程,标记-整理算法
- Concurrent Mark Sweep(CMS):优先最短回收停顿时间,用户体验好,标记-清除算法
- New and Old
- 关键点:region内存划分,优先级区域回收方式。优先回收价值最大的region
- Garbage First(G1):它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是一部分Region(不需要连续)的集合。
2.3 类装载子系统(Classloader sub-system)
定位和导入二进制class文件,校验导入类的正确性,为类变量分配并初始化内存,解析符号引用
- 启动类加载器(Bootstrap Classloader):负责加载
JAVA_HOME/lib
下的合法类文件 - 扩展类加载器(Extension Classloader):负责加载JAVA_HOME/lib/ext下的合法类文件
- 应用程序类加载器(Application Classloader):负责加载用户路径classpath下的合法类文件
- 自定义类加载器(User Defined Classloader):
class MyClassLoader extends ClassLoader
JVM通过双亲委派模型进行类的加载,先交给其最底层父类去加载,父类无法加载时才自己去加载:application classloader -> extension classloader -> bootstrap classloader
先看User Defined Classloader是否缓存了,如果缓存,就直接返回,如果没有,委派父Classloader去加载,如果父类缓存了,就直接返回,否则再委派给父classloader;知道最后的bootstrap Classloader,如果其缓存找不到,则在其路径下找,找到则至二级返回,否则往其子类返回,让其子类在其路径下找;最后又回到自定义Classloader,如果还找不到就抛异常
2.4 执行引擎(Execution Engine)(执行Classloader中的方法指令)
- 解析器(Interpreter):读取源码或字节码,并逐条直接执行(javac是在JVM外的)
- 即时编译器(Just-In-Time compiler,JIT):读取源码,更多时候是字节码,然后即时编译为机器码并执行
Java虚拟机是可运行java代码的假想计算机,java源文件(.java)通过java编译器javac生成字节码文件(.class),字节码文件(.class)通过JVM中的解释器再翻译成特定机器上的机器码
源码/源代码/Source code/.java -> 字节码/Bytecode/.class -> 机器码/Machine code/原生码/Native Code
2.5 运行时数据区(Runtime Data Areas)
JVM运行时需要从整个计算机内存中划出一块内存区域储存JVM需要用到的东西
- 堆(Heap)
JVM内共享/线程间共享,保存了所有类实例/对象本身instance,不存储基本数据类型对象和自定义对象引用(这些存在各线程的JVM Stack中),是GC的主要回收区
- 方法区(Method Area)/永久代(Permanent Generation)
JVM内共享/线程间共享,保存了每个类的信息(类的名称,字段信息,方法信息),class,静态变量static等
- 程序计数器(Program Counter Register)/PC计数器
县城内共享,保存每个线程正在执行的虚拟机字节码指令的地址;若该方法为Native的,则计数器置空Undefined
- 虚拟机栈(JVM Stack)/线程栈
线程内共享,保存基本数据类型的对象和自定义对象的医用,执行环境的上下文
- 本地方法栈(Native Method Stacks)
线程内共享,与JVM Stack所发挥的左右相似
其区别为:虚拟机栈为虚拟机执行java方法(字节码)的服务;而本地方法栈则是为虚拟机执行的Native 方法服务
3、垃圾回收器开启命令
Command Line | 新生代GC | 老年代GC |
-XX:+UseSerialGC | serial串行回收器 | serial Old穿行回收器 |
-XX:+UseParNewGC | ParNew并行回收器 | Default |
-XX:+UseParallelGC | Parallel并行回收器 | Default |
-XX:+UseParallelOldGC | Default | Parallel并行回收器 |
-XX:+UseConcMarkSweepGC | Default | CMS并行回收器 |
-XX:+UseG1GC | G1回收器 | G1回收器 |
根据上表新生代与老年代的GC模式互为搭配一起为垃圾回收器服务
默认的垃圾回收器(Default Garbage Collections):
- Java 7 - Parallel GC
- Java 8 - Parallel GC
- Java 9 - G1 GC
参考来源:https://mp.weixin.qq.com/s/iwSNc8xDmhqKx1GhGkrbaA