Jvm (java virtual machine) Java虚拟机,用以支撑Java一次编译导出运行的实现
Java8官网https://docs.oracle.com/javase/8/
Developer Guides https://docs.oracle.com/javase/8/docs/index.html 有如下图解
从图中可知,jdk 包好了jre,jre包含了jvm
从以前知识中我们知道,我们编写了一个Person.java 文件,
- 通过jdk 提供的javac命令将它编译成Person.class 文件,这个过程现在对我们是很熟悉的。
编译原理——词法分析,语法分析,语法树,字节码生成器,Person.class
- 将生成的Person.class 使用十六进制查看工具打开发现就是一堆十六进制码,
如何看这些十六进制码呢,在java官网中有介绍,Java虚拟机规范介绍 The class
File Format
如图每两个十六进制位标识u1 ,依次类推
Magic:以上U4代表magic,表示一个class文件的格式开头,所有的.class文件都是以cafebabe开头
minor_version,major_version :表示
此class
文件的次要和主要版本号,主版本号和次版本号共同决定class
文件格式的版本 。如果class
文件的主要版本号为M,次要版本号为m,则将其class
文件格式的版本表示为Mm。因此,class
文件格式版本可以按字典顺序排序,例如1.5 <2.0 <2.1,转成十进制
constant_pool_count:常量池数量
- 接下来就是将Person.class文件交给了jvm 去运行, 那么jvm如何读取Person.class文件呢,就是通过类加载机制
类加载机制流程:Loading, Linking, and Initializing
(1)类的装载,加载是查找具有特定名称的类或接口类型的二进制表示形式并从该二进制表示形式创建类或接口的过程
- 先找到class文件的位置,全路径, 通过类加载器加载类字节码文件,(ClassLoader)当然不同的ClassLoader扫描装载不同位置的文件,类加载机制
- 先将类文件的信息交给JVM,存储到jvm的(Method Area)方法区
- 再将类文件所对应的对象Class交给JVM,存储到jvm 的heap堆中,堆
(2)链接,链接是获取类或接口并将其组合到Java虚拟机的运行时状态以便可以执行的过程
1)验证,保证被加载的类的正确性
2)准备,要为类的静态变量分配内存空间,并将其值初始化位默认值
3)解析,将类中的符号引用转换为直接引用
(3)初始化,类或接口的初始化包括执行类或接口的初始化方法<clinit>
(第2.9节)
运行时数据区:Java虚拟机定义了在程序执行期间使用的各种运行时数据区域。其中一些数据区域是在Java虚拟机启动时创建的,仅在Java虚拟机退出时才被销毁。其他数据区域是每个线程的。在创建线程时创建每线程数据区域,在线程退出时销毁每个数据区域
运行时数据区 | ||
方法区(Method Area) | 虚拟机栈(JVM Stacks) | 本地方法栈(Native Method Stack) |
堆 | 程序计数器(The PC Register) |
- 方法区:在所有Java虚拟机线程之间共享,方法区只有一个,线程非安全,生命周期和虚拟机一样。它存储每个类的结构,存储类信息,常量,静态变量,即时编译器编译之后的代码。方法区域是在虚拟机启动时创建的,逻辑上是堆的一部分,方法区域的内存不必是连续的,可以扩展或缩小。每个运行时常量池都是从Java虚拟机的方法区域(第2.5.4节)分配的。当Java虚拟机创建类或接口(第5.3节)时,将构造该类或接口的运行时常量池
- 堆:在所有Java虚拟机线程之间共享, 线程非安全,存储对象实例或数组,堆是在虚拟机启动时创建的。对象的堆存储由自动存储管理系统(称为垃圾收集器)回收;
- 程序计数器:Java虚拟机可以一次支持多个执行线程,每个Java虚拟机线程都有其自己的
pc
(程序计数器)寄存器,PC计数器就是用来记录当前线程执行到了哪个方法 - 虚拟机栈:每个Java虚拟机线程都有一个私有Java虚拟机栈,与该线程同时创建,它保存局部变量和部分结果,并在方法调用和返回中起作用。Java虚拟机栈的内存不必是连续的
通过javap -c person.class 反编译字节码文件,可以查看字节码指令,然后结合Frame栈帧进行压栈弹栈的操作
Frame 栈帧:每次调用方法时都会创建一个Frame,方法调用完成时会被销毁,Frame空间是从栈内存中分配的,
- Local Variables 存储局部变量
- Operand Stacks 操作数堆栈
- Dynamic Linking
- Invocation Completion
Java对象内存布局:
对象头 | 实例数据 | 对齐填充 | ||
Mark Word 一系列的标记位(哈希码,分代年龄,锁状态标志等) 64位系统:8字节 | Class Pointer 指向对象对应的类元数据的内存地址
8字节 | Length数组对象持有
数组长度
4字节 | 包含了对象的所有成员变量,大小由各个变量的类型决定
| 为了保证对象的大小为8字节的整数倍 |
- 本地方法栈:通常在创建每个线程时为每个线程分配本机方法堆栈
JVM内存模型
方法区 Metespace | 堆 2(old):1(new) | |||
| Old 老年代 | Young 新生代 8:1:1 | ||
| Eden区 | S 0 (From) | S 1 (To) | |
|
|
|
年龄: 一次垃圾回收,岁数增加1
老年代:对象特别大,直接放到老年代,比如新生代只有100M,对象有120M,则直接放入老年代
垃圾回收:将超过一定年龄(默认15)的对象移入老年代
对象存储模型:当对象刚开始创建的时候,Eden区空间充足,将对象存储到Eden区,慢慢的Eden区空间不足,进行垃圾回收,将回收后剩余的对象移入S0区,Eden区空间又充足,开始存储对象,当Eden区再次空间不足的时候,又一次进行垃圾回收,回收过后将Eden区剩余对象和S0区对象全部移入S1区,然后将年龄大于15的移入Old区,Eden:S0:S1=8:1:1这样浪费10%的空间解决了空间碎片的问题,大多数对象都是“朝生夕死“。如果S0/1的空间不够的话就向Old区借空间——担保机制
垃圾回收:
YoungGC【包含了Eden,S区】:Minor GC,Eden区或者S区不够用会发生
Old GC: Major GC ,MajorGC通常会伴随着MinorGC ,也就意味着会触发FullGC,老年代不够用了会发生
Young + Old : Full GC,时间比较长,要尽可能的减少FullGC频率(允许一定范围的YongGC)
方法区
- 确定什么样的对象是垃圾
引用计数【会有循环引用的问题】,可达性分析【GC root 由它出发,某个对象是否可达】
- 该如何回收垃圾(垃圾回收算法)
- 优势和劣势该如何选型
- 学会查看垃圾回收的日志文件
垃圾回收机制:
GCroot:虚拟机栈中的本地变量表,static成员,常量引用,本地方法栈中的变量,类加载器,Thread,
垃圾回收算法:
- 标记和清除算法:
适用于少量对象存活的场景【Young区】
缺点:空间碎片内存不连续,标记和清除都比较耗时,效率比较低
- 复制算法:
缺点:空间浪费
优点:空间连续
- 标记整理算法:
新生代:复制算法
老年代:标记-清除/整理
CMS:并发标记扫描(CMS)收集器【用户应用线程和垃圾回收线程同时进行】,比较关注停顿时间
G1: https://blogs.oracle.com/poonam/understanding-g1-gc-logs 停顿时间可设置可控
停顿时间:垃圾收集器进行垃圾回收,终端执行响应的时间
吞吐量:运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
停顿时间小:CMS,G1 适用于web应用 并发类的收集器
吞吐量优先:Parallel Scanvent,Parallel Old 适用于跑任务 并行类的收集器
JVM参数:
- 标准参数:不会随着JDK版本的变化而变化
Java –verson/-help 等
- –X参数
非标准参数,会根据JDK版本的变化而变化
-Xint
- –XX参数
Boolean类型:-XX[+/-]name 启动或停止
非Boolean类型:-XX:name=vaule
- 其他参数
-Xms100M ===-XX:InitialHeapSize=100M
-Xmx200M ===-XX:MaxHeapSize=100M
-Xss100K === -XX:ThreadStackSize=100K
查看打印JVM参数配置
命令行:java –XX:+PrintFlagsFinal –version
程序启动参数:-XX:PrintFlagsFinal
Java命令:
Jps:查看Java进程
Jinfo:查看或修改jvm参数
eg: jinfo –flag MaxHeapSize 进程号
jinfo –flag name=vaule PID 条件:[managable]
查看曾经修改过的值jinfo –flag PID
Jstat: 查看jvm指标信息
Jstack:查看线程堆信息 jstack PID,如果线程发生问题,方便排查,Java代码中死锁
Jmap:生成堆内存快照,jmap –heap PID
OOM时导出堆内存信息生成dump文件:jmap –dump:format=b,file=heap.hprof PID
自动生成在程序执行时添加参数:-XX:+HeapDumpOnOutOfMemoryError –XX:HeapDumpPath=heap.hprof
查看heap.hprof文件的工具:Mat,在线工具console.perfma.com
Jconsole,jvisualvm,arthas
配置远程连接JVM参数
查看GC日志:GCViewer,gceasy.io
-XX:+PrintGCDetails –XX:+PrintGCTimeStamps –XX:+PrintGCDateStamps –Xloggc:gc.log
JVM调优:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html
CPU飙升:
内存空间不够用了:
GC次数太多,用户线程执行受影响:
堆的大小不够用了?那就调整一下堆大小,看看参数是否有变化
设置停顿时间,观察吞吐量和GC次数,找到最佳值
启动并发GC时(堆内存使用占比):-XX:+InitiatingHeapOccupancyPercent (默认45%)
G1 和CMS区别:CMD标记清除,G1采用标记整理