一、JVM与Java体系结构
1.1 JVM的整体结构
1.2 JVM的架构模型
1. 基于栈的指令集架构
- 设计和实现更简单,适用于资源受限的系统:
- 避开了寄存器的分配难题: 使用零地址指令方式分配。
- 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器容易实现。
- 不需要硬件支持,可移植性更好,更好实现跨平台
2. 基于寄存器的指令集架构
- 典型的应用是x86的二进制指令集: 比如传统的PC以及Android的Davlik虚拟机。
- 指令集架构则完全依赖硬件,可移植性差
- 性能优秀和执行更高效:
- 花费更少的指令去完成一项操作。
- 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主。
1.3 JVM的生命周期
- 启动:由引导类加载器bootstrap class loader加载初始类(init class)完成的,这个类是由虚拟机具体制定的
- 执行:执行主要是执行java指令,java指令执行完了虚拟机也就结束了
- 结束:虚拟机自己结束、异常结束、System.exit(0)结束
1.4 JVM发展历程
1. Sun Classic VM
执行引擎:
- 解释器:逐行解释字节码,类似步行,一直在运行不卡顿但是比较慢
- JIT:翻译本地机器指令,翻译完后速度快。类似公交车,刚开始要等,后面很快
2.Exact VM
3. HotSpot VM(重点:三大虚拟机3,4,5)
4. JRockit
5. IBM 的 J9
6. KVM、CDC、CLDC的介绍
7. Azul VM 和Liquid VM
特点:针对特定硬件做了优化,耦合度高,所以性能特别好,但是应用场景有限
8. Apache Harmony
9. Microsoft JVM 和 TaobaoJVM
10.Dalvik VM
具体JVM的内存结构,其实取决于其实现,不同厂商的JVM,或者同一厂商发布的不同版本,都有可能存在一定差异。本套课程主要以oracle HotSpot VM为默认虚拟机。
11. Graal VM
二、类加载子系统
先看一下内存图
复杂版:
1.作用
类加载过程:
1. 类的加载过程一:加载Loading
- 通过一个类的全限定名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
方法区:1.7以前叫永久代 1.8开始叫元空间 (都是方法区的实现
2. 类的加载过程二:链接
3. 类的加载过程三:初始化
2. 类加载器的分类
1. Bootstrap ClassLoader
启动类(引导类加载器
2. Extension ClassLoader
3. AppClassLoader
用户自定义类加载器
- 在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。
- 为什么要自定义类加载器?
– 隔离加载类
– 修改类加载的方式
– 扩展加载源
– 防止源码泄漏
p33
ClassLoader
3.双亲委派机制
小总结:在java中存在很多的加载器(class
loader),当一个类加载器收到了加载请求,他会先把请求委托给父类去执行,如果父类还有父类就一直委托,然后从最高父类往下,谁要加载谁加载
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理I它是一种任务委派模式。
4. 双亲委派机制优势
5. 沙箱安全机制
自定义string类,但是在加载自定义string类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载idk自带的文件(rt.jar包中java
lang\string.class),报错信息说没有main方法就是因为加载的是rt.jar包中的string类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。
6. 其他
三、运行时数据区概述及线程
内存是非常重要的系统资源,是硬盘和CPU 的中间仓库及桥梁承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。不同的JVM对于内存的划分方式和管理机制存在着部分差异。结合JVM虚拟机规范,来探讨一下经典的JVM内存布局。
2. 线程
守护线程、普通线程
3. 程序计数器(PC寄存器)
1.介绍
只有PC寄存器没有OOM
- 介绍
- 作用:
- 因为只是一个地址所以很小
2. 举例说明
3. 常见面试题
4. 虚拟机栈
重要:栈、堆、方法区
1. 概述
2. 虚拟机常见异常
3. 栈的存储单位
4. 栈帧
4.1. 局部变量表(local variables)
4.1.1. 变量槽slot的理解与演示
省流:float、double占两个slot槽
4.1.2. 静态变量与局部变量对比的小结
局部变量:在使用前,必须要进行显式赋值!否则编译不通过
4.2. 操作数栈(Operand Stack)
代码追踪:
4.2 动态链接
指向运行时常量池的引用
4.3 方法的调用
4.2 虚方法与非虚方法
省流:能被重写的都是虚方法,只有不能被重写的才是非虚方法
4.3 invokedynamic指令的使用
动态类型:js、python
静态类型:java
省流:使用lambda表达式会出现invokedynamic
4.4 方法重写的本质与虚方法表的试用
省流:
为什么要建立虚方法表?
因为在面向对象的编程中,会很频繁的使用到动态分派,每次加载某个方法都要先找他本身,然后再找他的直接父类,父类没有再网上一直找,很影响效率,因此为了提高性能,在方法区建立了虚方法表
省流:重写过的方法,在虚方法表中指向自己,没重写过的直接指向父类的方法,就不用一层一层往上找了
4.5 方法返回地址
4.6 一些附加信息
4.7 面试题+总结
5. 本地方法接口
本地方法是有方法体的,只不过方法体是c/c++实现的
6. 本地方法栈
7. 堆(重要)
1. 堆的核心概述
2. 堆的细分内存结构
3. 设置堆内存大小与OOM
4. OOM的举例说明
5. 年轻代与老年代
6. 图解对象分配过程
省流:
- 年龄计数器:每移动一次年龄计数器+1,到15(默认值,可设置)的时候移动到老年代,到老年代以后年龄计数器就没用了
- 幸存者s0,s1,from,to:复制之后有交换,谁空谁是to,每次Eden过来的移动到to区
- 触发YGC/Minor GC的时机:Eden区满,对Eden区和幸存者区同时进行YGC的判断,存活下来的年龄计数器+1
- 垃圾收集规律:频繁在新生区收集,很少在养老区收集,几乎不在永久区元空间收集
7. 对象分配的特殊情况
代码演示对象分配过程:
8. 常用调优工具
9. Minor GC、Major GC与Full GC
调优:希望GC情况少一些,因为GC的时候会导致用户进程(执行程序的进程)的暂停(STW)
- Minor GC:
10. GC举例与日志分析
流程:先往新生代中的Eden区添加数据,等到Eden区满以后进行Young GC/Minor GC,将结果放到s0、s1区,等到年龄计数器等于15,放到老年代中,如果老年代满触发full gc
-Xms9m -Xmx9m -XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen: 2039K->480K(2560K)] 2039K->764K(9728K), 0.0031412 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2220K->512K(2560K)] 2504K->1188K(9728K), 0.0012581 secs] [Times: user=0.00 sys=0.02, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2470K->496K(2560K)] 3146K->1940K(9728K), 0.0015030 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1302K->496K(2560K)] 7354K->6564K(9728K), 0.0012464 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 496K->480K(2560K)] 6564K->6564K(9728K), 0.0025705 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 480K->0K(2560K)] [ParOldGen: 6084K->3712K(7168K)] 6564K->3712K(9728K), [Metaspace: 3295K->3295K(1056768K)], 0.0088732 secs] [Times: user=0.00 sys=0.02, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 38K->32K(1536K)] 6822K->6816K(8704K), 0.0011019 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(1536K)] [ParOldGen: 6784K->5248K(7168K)] 6816K->5248K(8704K), [Metaspace: 3295K->3295K(1056768K)], 0.0068286 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5248K->5248K(9216K), 0.0011081 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5248K->5226K(7168K)] 5248K->5226K(9216K), [Metaspace: 3295K->3295K(1056768K)], 0.0100162 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2048K, used 80K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 7% used [0x00000000ffd00000,0x00000000ffd14098,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 7168K, used 5226K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 72% used [0x00000000ff600000,0x00000000ffb1abb0,0x00000000ffd00000)
Metaspace used 3327K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 362K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at src.Test1.main(Test1.java:13)
11. 小结:堆空间分代思想
12. 内存分配策略
13. 为对象分配内存:TLAB
14. 小结:堆空间的参数设置
- 官网说明:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html - -xx:+PrintFlagsInitial : 查看所有的参数的默认初始值
- -xX:+PrintFlagsFinal :查看所有的参数的最终值 (可能会存在修改不再是初始值)
- 具体查看某个参数的指今:
jps: 查看当前运行中的进程
jinfo -flag SurvivorRatio 进程id - -xms:初始堆空间内存(默认为物理内存的1/64)
- -Xmx:最大堆空间内存(默认为物理内存的1/4)
- -Xmn:设置新生代的大小。(初始值及最大值)
- -xX:NewRatio:配置新生代与老年代在堆结构的占比
15. 通过逃逸分析看堆空间的对象分配策略
省流:
总结:如何快速的判断是否发生了逃逸分析,
大家就看new的对象实体是否有可能在方法外被调用
//发生了逃逸,不建议这样写(无法使用栈上分配)
public StringBuffer method1(){
StringBuffer str = new StringBuffer();
return str;
}
//未发生逃逸,建议这样写以避免一些GC
public String method1(){
StringBuffer str = new StringBuffer();
return str.toString();
}
结论:
开发中能使用局部变量的,就不要使用在方法外定义
16. 代码优化之栈上分配
栈上分配:
同步省略:
分离对象或标量替换:
总结: