java 官方文档 https://docs.oracle.com/javase/specs/index.html
JVM 大纲
javac的过程
java
-> javac
-> class
-> JVM
词法分析器
->语法分析器
->语法树/抽象语法树
->语义分析
->注解抽象语法树
->字节码生成器
->class文件
类的加载过程
加载
->校验
->准备
->解析
->初始化
->使用
->卸载
- 加载
- 加载阶段是类加载过程的第一个阶段。在这个阶段,JVM 的主要目的是将字节码从各个位置(网络、磁盘等)转化为二进制字节流加载到内存中,接着会为这个类在 JVM 的方法区创建一个对应的 Class 对象,这个 Class 对象就是这个类各种数据的访问入口。
- 校验
- JVM规范校验
- 代码逻辑校验
- 准备
- 内存分配的对象(在准备阶段,JVM 只会为「类的static变量」分配内存)
- 初始化的类型 在准备阶段,JVM 会为类变量分配内存,并为其初始化。但是这里的初始化指的是为变量赋予 Java 语言中该数据类型的零值,而不是用户代码里初始化的值。
- 解析
- 当通过准备阶段之后,JVM 针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类引用进行解析。这个阶段的主要任务是将其在常量池中的符号引用替换成直接其在内存中的直接引用。
- 初始化
- JVM 遇到下面 5 种情况的时候会触发初始化
- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化
- JVM 遇到下面 5 种情况的时候会触发初始化
- 使用
- 当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。
- 卸载
- 当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存
类的加载器
- bootstart classload (根加载器负责加载java核心包比如rt.jar 或java,javax,sum开头的包,不能加载自定义类)
- extension classload(加载java平台扩展jar包比如 java/lib/ext目录下的jar)
- application classload(应用程序默认的加载器比如 classpath)
双亲委派机制
- 总结:
- 递归调用,向上委派,向下尝试
- 避免类重复加载
- 保护程序安全,防止核心的java语言环境遭受破坏
初始运行时数据区
- 静态方法区(共享资源线程不安全)
- 运行时数据区(运行时线程安全数据区)
执行引擎
- interpreter
- JIT Compiler(即时解释权)
- Garbage Collection(垃圾回收器)
运行时数据区概述
- 数据区
- 方法区
- 堆
- 运算区
- 程序计数器
- 虚拟机栈
- 本地方法栈
运算区
-
程序计数器
- 一个很小内存区域用于保存当前线程所执行的字节码的行号(内存地址)
- 特点:
- 每个线程都有自己私有的程序计数器
- 唯一一个没有OOM的区域
- 生命周期与线程一致 生命周期随着线程,线程启动而产生,线程结束而消亡
-
虚拟机栈
- Java虚拟机栈的生命周期与执行的线程一致
- Java虚拟机栈是当前执行线程独占空间。以栈的数据结构形式存在
- Java虚拟机栈是线程运算执行的区域,它保存着一个线程方法调用的顺序和过程
- 被线程执行的方法,都是以栈帧(frame)的结构压入Java虚拟机栈
- 当Java虚拟机栈的空间不够使用时,将报出Stack Overflow Exception
-
栈帧
- 局部变量
javap -v xxx.class
可以查看方法的本地变量表 locals
- 操作数栈
- 动态链接
- 返回方法地址
- 局部变量
虚拟机的指令集架构
- 栈式指令集架构(JVM)
- 指令大部分是零地址指令,其执行过程完全依赖于Java虚拟机栈中的操作数栈
- 由于是零地址指令,生成的指令空间占用更少
- 不受限于物理的硬件资源,可移植性强更好的实现跨平台性
- 寄存器指令集架构(Davlik)
- 指令采用一地址指令,二地址指令,三地址指令,其执行过程依赖于硬件寄存器
- 指令空间占用更多,但是完成的功能可以更复杂(花更少的指令完成更复杂的操作)
- 性能更为优秀,执行更为高效
- 指令依赖硬件资源,可移植性较大限制
本地方法栈(Native标识的方法比如Object类中的 hashCode方法)
- 本地方法栈和虚拟机方法栈区别是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈是虚拟机调用Native方法的过程记录
数据区
方法区
- 方法区的生命周期与JVM进程一致
- 存储已被虚拟机加载的类型信息,方法信息,域信息,运行时常量,静态变量(不同版本不一样),即时编译器(JIT)编译后的代码
- 运行时常量池属于Method Area中的一部分
- 方法区从逻辑上来理解其本身也属于Heap的一部分。Method Area又称为Non-Heap主要目的是为了区分理解(JDK8之前的Method Area实现叫 Perm Space,JDK8之后的Method Area实现叫Meta Space)
- 方法区内存不足时,将抛出OutOfMemoryError
各版本变化
Heap(堆)
- 堆是生命周期与JVM一致
- 堆是Java虚拟机运行时数据区共享数据区最大的区域
- “几乎”所有的对象和数组都在堆中进行分配
- 堆是JVM GC工作重点区域
- 堆内存不足时,将抛出OutOfMemoryError
Heap的分代模型
数据区的划分
Java对象的布局
Java对象的SIZE
Java对象的一生
Java对象分配的特例
- 栈上分配
- 针对不会逃逸出方法的对象,JVM允许将对象属性打散后分配在栈(线程私有的,属于栈内存)上这种方式叫做栈上分配
- XX:+DoEscapeAnalysis
- TLAB(Thread Local Alloction Blocks)
- 在Eden区开辟每一个线程私有的很小的缓冲空间,后续线程需要创建对象只要TLAB空间能放下就会在此空间进行创建,避免同步(串行),提升效率
- -XX:UseTLAB
垃圾回收与垃圾回收器
- 定位垃圾对象
-
使用可达性分析定位垃圾对象,在GC ROOT引用链之外的对象被视为垃圾对象,解决了引用计数循环依赖的问题
-
由GC ROOT 图例可以看出 09 07 08是在引用链之外所有会被GC
-
GC的相关概念
- 垃圾回收分类
- Minor GC:新生代 指Young区的垃圾回收过程
- Major GC:老年代 指Old区的垃圾回收过程
- FUll GC:新生代+老年代 Young和Old区一起执行垃圾回收过程
- MajorGC不单独存在MajorGC都会伴随着Minor GC
- 触发垃圾回收:
- 当Eden区或者s区不够用
- 老年代空间不够用
- 方法区空间不够用
- 调用System.gc()
GC是有JVM自动完成,根据JVM系统环境而定,触发时间是不确定的
对象的引用类型
-
强引用
- 默认情况,就算是出现OOM也不会对改强引用对象回收
-
弱引用(WeakReference)
- 发生GC的操作就会被垃圾回收
-
软引用(SoftReference)
- 内从不足时才会被回收
-
虚引用(PhantomReference & ReferenceQueue)
- 主要是用跟踪对象被垃圾回收的情况
垃圾回收算法
-
标记清除算法
-
缺点:会产生大量空间碎片
-
标记整理算法
-
复制算法
垃圾收集器
- 年轻代垃圾收集器(JKD8以前)
Serial
ParNew
Parallel Scavenge
- 均使用标记复制算法
- 老年代垃圾收集器(JDK8以前)
CMS
Serial Old
Parallel Old
- 其中
CMS
用的是标记清除算法 Serial Old
和Parallel Old
和Serial Old
使用的标记整理算法
垃圾收集器的搭配
-
Serial & Serial Old
- Serial是针对新生代的垃圾收集器,采用复制算法
- Serial Old是老年代垃圾收集器,采用的是标记整理算法
- Serial这套垃圾收集器特点是单线程串行垃圾收集器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LUCf3fnl-1605154052561)(pic/Serial&SerialOld.png)]
-
ParNew
- ParNew垃圾收集器是新生代的多线程并行的垃圾收集器
- ParNew可理解为Serial收集器的多线程版本
-
Parallel Scavenge
- Parallel Scavenge收集器是一个多线程并行新生代收集器
- Parallel Scavenge与ParNew的工作机制基本相同
- Parallel Scavenge更关注系统的吞吐量
吞吐量 = 用户工作时间/(用户工作时间 + GC时间)
User Time 99s
GC STW TIME 1s
吞吐量=99/(99+1)=99%
-XX:MaxGCPauseMillis=500 设置GC STW(停止用户工作线程 stop the world)的时间,强制中断用户线程
(JDK8默认垃圾收集器 Parallel Scavenge
& Parallel Old
)
-
CMS (Concurrent Mark Sweep)
- CMS垃圾收集器是Old区采用标记-清除算法的垃圾收集器
- 是第一个实现并发垃圾收集的垃圾收集器
- 设计目标以最短STW停顿时间,发挥多核CPU并发运行为设计原则
- 工作流程:
-
初始标记-(需要STW极短的时间停止)GC ROOT第一层
-
并发标记-(GC标记线程与用户线程一起工作)
-
重新标记-(需要STW修正的工作)
-
并发清理-(GC清理线程与用户线程一起工作)
-
优点:
- 充分利用CPU资源并发收集,低停顿
-
缺点:
- 1.标记-清除算法将产生空间碎片问题
- 2.并发标记和并发清理阶段由于并不是全力GC工作一定会带来GC时间过长,影响吞吐量
- 3.清理不彻底,会产生浮动垃圾,且浮动垃圾只能在下一次垃圾回收才能清理
-
-
Parallel Old
- Parallel Old是Parallel Scavenge的老年代垃圾收集器,采用的是标记整理算法
- Parallel Old是多线程并行的垃圾收集器
- PS + PO:
-
G1(Garbage First)
- https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html
- G1垃圾收集器是适用于多核处理器,大内存容量的服务端系统
- 追求可控制的(短时间)GC停顿同时送达到一个高的吞吐量
- 且解决了CMS垃圾收集器中的针对promotion failure 和 concurrent mode faild.因为其垃圾回收算法本质采用复制算法
G1的内存结构不再是连续的Old Eden S1 S2(分代模型)了.而是改用Region化的内存分布Region大小设置为1M-32M中的2的N次幂(1,2,4,8,16,32)每一个Region不再固定的划分为某个区域.基于运行情况进行改变Eden,Survivor,Old,Humongous(>=0.5*Region的对象)
- G1的YoungGC
垃圾回收器总结
JVM调优
-
https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
-
JVM的参数配置
- 标准参数(-)
-version
-help
-server
/client
-DpropertyName=value
(系统变量设置)-classpath
/cp
- …
- 标准参数(-)
-
JVM的参数配置
- 非标准参数(-X)
-Xms512M
(最小堆大小)-Xmx512M
(最大堆大小)-Xmn200M
(最小新生代大小)-Xss512K
(虚拟机栈)
- 非稳定型参数(-XX:)
-
赋值参数
-XX:ThreadStackSize=512k(
JAVA虚拟机栈大小)-XX:MaxGCPauseMillis=500
(设置垃圾回收器STW时间)-XX:InitialHeapSize=1000M
(初始堆大小)
-
布尔参数(+开启-关闭)
-XX:+PrintFlagsFinal
(查看所有可设置的非稳定性参数,其中manageable
,producer rw
标识的参数是可以动态修改的,:=
表示参数被修改过)-XX:+DoEscapeAnalysis
(开启对象逃逸分析)-XX:+UseTLAB
(开启TLAB对象缓冲)-XX:+UseCompressedOops
(开启压缩指针)
-
- 非标准参数(-X)
-
jps
- 列出当前系统中运行的JVM进程
jps
- 不带参数,默认显示进程ID和启动类的名称
jps -m
- 输出传递给Java进程(main方法)的参数
jps -l
- 输出主函数的完整路径(类的全路径)
jps -v
- 输出传递给JVM的参数
- 列出当前系统中运行的JVM进程
-
jinfo
-
输出配置信息(命令行参数,系统变量)及运行阶段修改配置信息
jinfo -flag <name> {PID}
- 查看是否使用 G1垃圾回收,
jinfo -flag UseG1GC PID
-XX:-UseG1GC
-UseG1GC 表示没有启用,+UseG1GC 表示启用- 运行时动态配置参数
jinfo -flag +PrintGC PID
,只能修改manageable
,producer rw
标识的参数
- 查看是否使用 G1垃圾回收,
jinfo -flags {PID}
(查看修改过的JVM配置参数)jinfo -sysprops {PID}
(查看当前进程的运行情况和配置信息)jinfo -flag [+|-] {PID}
(设置布尔参数)jinfo -flag <name>=<value> {PID}
(设置赋值参数)
-
在程序运行时所有
manageable
,producer rw
标识的参数可以通过jinfo -flag 参数 PID
动态设置
-
-
jstat
- 对JVM应用程序的资源和性能进行实时的反馈
jstat [-命令选项]{PID}[间隔时间][查询次数]
jstat -options
:获取jstat可以获取哪些信息的监控- -class
- -compiler
- -gc
- -gccapacity
- -gccause
- -gcmetacapacity
- -gcnew
- -gcnewcapacity
- -gcold
- -gcoldcapacity
- -gcutil
- -printcompilation
- 示例:
jstat -class PID 1000 20
(查看间隔1s钟输出20次类加载的数量)
-
jstack
-
线程跟踪工具,用于获取指定JVM进程的线程堆栈信息
-
用法:
jstack {PID}
- 排查线程死锁问题
- 排查CPU飙升问题
-
CPU飙升排查
-
1.基于Linux命令
top
查看CPU进程使用情况
-
2.
top -Hp PID
(查看线程情况)
-
3.找到线程占用最高的PID使用计算器计算PID的十六进制,经过计算得出PID的十六进制是7702
-
4.
jps
找到应用的进程,jstack PID | grep 7702 -A20
A20表示显示20行信息
-
-
最后定位CPU飙升问题出现在LoopDemo.java at 8 line
-
-
jmap
- 输出JVM某一时刻运行快照 [内存,GC情况,对象存活情况]
jmap -heap {PID}
- 查看GC使用的算法,heap(堆)的配置及JVM堆内存的使用情况
jmap -histo:live {PID}
- 查看JVM中各个[live]对象数量,大小
jamp -dump:live,format=b,file=D://heap//test.hprof {PID}
- dump堆里的对象信息和内存信息,进行相关分析(慎用,因为会暂停用户线程即:STW)
- 输出JVM某一时刻运行快照 [内存,GC情况,对象存活情况]
-
.hprof 文件分析工具
- 1.
jhat -port 8888 test.hprof
(JDK自带工具) - 2.Eclipse插件MAT分析工具
- 3.在线的hprof分析工具
- 1.