深入理解Java虚拟机
2021-07-07 13:21:24 12 举报
AI智能生成
深入理解Java虚拟机解析思维导图
作者其他创作
大纲/内容
概述
面向人员
中、高级开发人员
系统调优师
系统架构师
参考书籍
《The Java Language Specification,Java SE 7 Edition》
《Java虚拟机规范 JavaSE7》
《Java语言规范》
评:大致看了下此书目录,几乎都是书写规范。
《Oracle JRockit The Definitive Guide》
《Oracle JRockit权威指南》
网络资源
高级语言虚拟机
http://hllvm.group.iteye.com/
RednaxelaFX博客
http://rednaxelafx.iteye.com/
Oracle官网
走进Java
技术体系
广义
Clojure
JRuby
Groovy
平台
Java Card
Java ME
Java SE
Java EE
JDK
包含
Java程序设计语言
Java虚拟机
Java API类库
商业机构
开源社区
区别
OpenJDK
几乎包含了SunJDK的全部代码,除了极少部分无法进行开源的代码外
SunJDK
版权注释与OpenJDK不一致
JRE
Java虚拟机
Java SE API
发展历史
JDK
JDK1.6(重大更新)
提供动态语言支持(通过内置Mozilla JavaScript Rhino引擎实现)
虚拟机内部做了大量改进
锁与同步
垃圾收集
类加载
JVM
Sun Classic VM
1996年Sun公司发布JDK1.0所包含,已淘汰
世界上第一款商业Java虚拟机
JDK1.2的时候与HotSpot共存,到了JDK1.3的时候,HotSpot变为默认,而Sun Classic VM变为候选,JDK1.4的时候被淘汰
Exact VM
JDK1.2时Solaris平台发布
很快被更优秀得HotSpot VM取代,甚至没有来得及发布Windows和Linux平台下得版本
HotSpot VM
由Longview Technologies小公司设计,1997年Sun公司收购了LT公司,从而获得了HotSpot VM
2006年JavaOne大会上宣布开源
SunJDK和OpenJDK共同的虚拟机
特性
热点探测
条件
如果一个方法被频繁调用
或者方法中有效循环次数很多
就会出发标准编译和OSR(栈上替换)编译动作
方式
基于采样
基于计数器
JRockit VM
由BEA公司在2002年从Appeal Virtual Machines公司收购的虚拟机
专门未服务器硬件和服务器应用场景高度优化的虚拟机,由于专注服务器端应用,不关注启动速度,内部不包含解析器实现
全部代码靠即时编译器编译后执行
在2008年,被Oracle收购(同时包括Sun也包括HotSpot虚拟机)
将JRockit VM优秀特性移植到HotSpot上
垃圾回收器
MissionControl服务
IBM J9 VM
和HotSpot类似,多用途虚拟机
Azul VM
Azul Systems公司在HotSpot基础上进行大量改进,运行于Azul Systems公司的专有硬件Vega系统上的Java虚拟机
每个Azul VM实例都能管理数十个CPU和数百GB内存的硬件资源
提供在巨大内存范围内实现可控的GC时间
Liquid VM
现在JRockit VE
BEA公司开发,可以直接运行在自家Hypervisor系统上
不需要操作系统支持(自身实现了一个传用操作系统的必要功能)
Apache Harmony/Google Android Dalvik VM
没有推广成功,但很多代码被吸纳到了Google Android SDK中
Microsoft JVM
早期Windows中性能最好的虚拟机
但最后被Sun公司起诉微软导致最终没有流行起来
其他(小众)
KVM
在Android、IOS等智能手机出现前曾经在手机平台上得到非常广泛的应用
CDC/CLDC HotSpot Implementation
Java ME的重要支柱
Squawk VM
Sun公司开发,曾经运用于Java Card
JavaInJava
1997-1998年sun开发的实验性质虚拟机
试图以Java语言实现虚拟机
Maxine VM
和JavaInJava相似
JamVM
cacaovm
SableVM
Kaffe
Jelatine JVM
NanoVM
MRP
Moxie JVM
Jikes RVM
未来
模块化
Java9已经支持
混合语言
推动Java虚拟机从“Java语言的虚拟机”向“多语言虚拟机”的方向发展
多核并行
Java8中的Lambda表达式
函数式编程的一个重要优点就是这样的程序天然地适合并行运行
GPU/APU运算
OpenJDK的子项目:Sumatra
丰富的语法
比如Lambda表达式
64位虚拟机
内存问题,指针膨胀
数据类型对齐补白
运行于64位系统的Java应用需要消耗额外的内存,通常比32位额外增加10%-30%
运行速度落后于32位虚拟机,两者大约有15%左右的性能差距
编译JDK
OpenJDK&Sun/OracleJDK
编译
获取源码
Linux
yum -y install hg
hg clone http://hg.openjdk.java.net/jdk7u/jdk7u-dev/
cd jdk7u-dev/
chmod 755 get_source.sh
./get_source.sh
略
查看源码
利用Clion打开
目录说明
网络资源
OpenJDK WiKi
内存模型
线程共享区
堆空间
概述
Java虚拟机规范:所有的对象实例以及数组都要在堆上分配,
但随着JIT编译器的发展,所有对象在堆上也渐渐变得不是那么绝对了
逃逸分析
逃逸分析的基本行为就是分配对象动态作用域
方法逃逸
当一个对象在方法中被定义后,他可能被外部方法所引用,称为方法逃逸
线程逃逸
甚至还可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸
栈上分配
将方法中的变量和对象分配到栈上,方法执行完成后自动销毁,而不需要垃圾回收的介入,从而提高系统性能。
标量替换
生命周期
生命周期与JVM生命周期一致
异常
OutOfMemoryError
分代
新生代
伊甸园(Eden space)
幸存0区(Survivor)
幸存1区
老年代
JVM参数
初始大小
Xms
最大空间
Xmx
### 描述
Java虚拟机管理的最大一块内存,虚拟机启动时创建,此内存区域的唯一目的就是用于存放对象实例。
Java堆是垃圾收集器管理的主要区域。
### 别名
GC堆
方法区
概念
分代
永久代
配置参数
-XX:MaxPermSize
-XX:PermSize
异常
OutOfMemoryError
运行时常量池
String.intern()
将字符串增加到常量池中
已存在
不存在
new出来的字符串,不会放在常量池中
存储信息
类信息
常量
静态变量
即时编译器编译后的代码
线程私有区
程序计数器
是什么?
通俗讲,就是用于记录当前线程下的程序运行到第几行的东东
异常
此内存区域是唯一一个在Java虚拟机规范中
没有任何OutOfMemoryError情况的区域
为什么?
每条线程一个计数器,而行号不可能过大,
所以理论上可以认为不存在溢出问题
生命周期
生命周期与线程相同
虚拟机栈
是什么?
可以大致理解为,它存储了整个方法的行为,以及实现这些行为所需要的条件(参数)
异常
StackOverFlowError
OutOfMemoryError
生命周期
生命周期与线程相同
存储信息(栈帧)
栈帧
局部变量表
容量
变量槽(Variable Slot)
boolean
byte
char
short
int
float
reference(对象实例的引用)
从此引用中直接或间接的对象在Java堆中的数据存放的原始地址索引
此引用中直接或间接的查找对象所属数据类型在方法区中存储的数据类型信息
returnAddress
作用
方法参数
方法内定义的局部变量
操作数栈
字节码指令,先入后出
调用其它方法的时候是通过操作数栈来传递参数的
动态链接
方法出口(方法返回地址)
概述
方法退出的过程实际上就等等与把当前栈帧出栈,退出可能执行的操作
退出种类
正常完成出口
执行引擎遇到任意一个方法返回的字节码指令
异常完成出口
方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理
额外附加信息
本地方法栈
异常
StackOverflowError
OutOfMemoryError
直接内存
异常
OutOfMemoryError
内存管理
Java对象
对象创建流程
第一步
检查常量池
检查类加载、解析、初始化
是
/
否
那必须先执行相应的类加载过程
空间分配
指针碰撞
Serial
ParNew
空闲列表
CMS
基于Mark-Sweep算法
分配内存的指针安全性
指针并非是安全的
指针策略
CAS
TLAB
初始化内存空间为零值
设置对象属性
完成对象创建
第二步
设置对象信息
初始化类信息
对象内存布局
对象头(Header)
自身信息(Mark Word)
哈希码(HashCode)
GC分代年龄
锁状态标志
线程持有的锁
偏向线程ID
偏向时间戳
类型指针
指向它的类元数据的指针
实例数据(Instance Data)
对齐填充(Padding)
对象的访问定位
方式
句柄
对象实例数据
类型数据各自的具体地址信息
直接指针
区别
异常
OutOfMemoryError
Java堆
-Xms
-Xmx
虚拟机栈和方法栈
参数
-Xss
单线程
实际一般抛出的异常都是StackOverflowError
多线程
StackOverflowError
OutOfMemoryError
方法区和运行时常量池
参数
参照永久区
本机直接内存
-XX: MaxDirectMemorySize
特点
垃圾收集器
哪些内存需要回收?
什么时候回收?
分析
引用计数器算法
很多Java虚拟机并没有采用这方式
可达性分析
GC Roots
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象
额外
引用
强引用
软引用
弱引用
虚引用
判断
第一次标记
是否有必要执行finalize()方法
第二次标记
如何回收?
回收堆空间
回收方法区
废弃常量
无用的类
回收条件
(不一定回收)
该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象 没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
回收
有哪些收集器?
ParallelOld
SerialOld
PSMarkSweep
ParallelScavenge
DefNew
ParNew
G1New
ConcurrentMarkSweep
G1Old
GCNameEndSentinel
垃圾收集算法
常见算法
标记-清除(Mark-Sweep)
复制算法
用于新生代
特点
标记-整理
用于老年代
分代收集
一般用于商业虚拟机
Hotspot算法实现
枚举根节点
Oopmap
安全点
中断方式
抢先式中断
主动式中断
安全区
常见垃圾收集器
Serial收集器
最早的收集器,单线程收集器
GC的时候会暂停所有用户线程
ParNew收集器
开启
默认
-XX:+UseConcMarkSweepGC
强制
ParallelScavenge收集器
新生代收集器
特点:关注吞吐量
-XX:MaxGCPauseMillis
收集最大吞吐停顿时间,接受一个大于0的ms数
-XX:GCTimeRatio
设置最大吞吐量,接受一个大于0小于100的整数
-XX:+UseAdaptiveSizePolicy
Serial Old收集器
Parallel Old收集器
CMS(Concurrent Mark Sweep)收集器
工作流程
初始标记
并发标记
重新标记
并发清除
G1收集器
Remembered Set
工作流程
初始标记
并发标记
最终标记
筛选回收
小知识点
并发
并行
GC常见参数
分配与回收
分配
对象优先在Eden分配
大对象直接进入老年代
长期存活的对象将进入老年代
动态对象年龄判定
回收
新生代GC(Minor GC)
老年代GC(Major GC)
整个堆(Full GC)
JDK工具
JPS
主要用来查询jvm的pid
常用选项:-v
jstat
常用参数-class
结果解读
jinfo
实时调整和查看虚拟机参数
jmap
用于生成堆栈快照
获取虚拟机堆栈快照
-XX:+HeapDumpOnOutOfMemoryError
可以让虚拟机在OOM异常出现后自动生成dump文件
-XX:+HeapDumpOnCtrlBreak
此参数可以使Ctrl+Break键让虚拟机生成dump文件
Kill -3
Linux系统下通过此命令可以发送进程退出信号,虚拟机也会生成dump文件
可以查询finalize执行队列
Java堆和永久代的详细信息
jhat
虚拟机堆转储快照分析工具
jstack
Java堆栈跟踪工具,用于生成虚拟机当前时刻的虚拟机快照
类文件结构
数据类型
无符号数
基本数据类型
组成
u1
1个字节
u2
2个字节
u4
4个字节
u8
8个字节
描述内容
数字
索引引用
数量值
安卓UTF-8编码构成字符串值
表
复合数据类型,整个class文件实质上就是一张表
组成
无符号数
表
魔数与Class文件版本
结构
魔数
描述
每个Class文件的头四个字节
作用
唯一作用就是确定这个文件是否为一个能被虚拟机接受的Class文件
内容
版本号
主版本号
次版本号
常量池
描述
Class文件之中的资源仓库
它是Class文件结构中与其他项目关联最多的数据类型
占用Class文件空间最大的数据项目之一
Class文件中第一个出现的表类型数据项目
结构
常量池计数值
类型
内容
字面量
类似java语言层面的常量,比如文本字符串,申明为final的常量值
符号引用
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
数据结构
访问标志
描述
用于识别一些类或者接口层次的访问信息
内容
索引
描述
通过它能够确定这个类的继承关系
内容
类索引
用一个u2类型的数据表示
色i索引用于确定这个类的全局限定名
父类索引
用一个u2类型的数据表示
用于确定这个类的父类的全局限定名
接口索引
一组u2类型的数据集合
字段表集合
描述
用于描述接口或者类中声明的变量
方法表集合
属性表集合
字节码指令
概述
构成
Java虚拟机的指令由一个字节长度的代表着某种特定操作含义的数字(操作码:Opcode)以及跟随其后的零至多个代表此操作所需参数(操作数:Operands)构成。
常见指令
加载和存储
概述
加载和存储指令用于将数据在针栈中的局部变量表和操作数栈之间来回传输
指令
将一个局部变量加载到操作栈
将一个数值从操作数栈存储到局部变量表
将一个常量加载到操作数栈
扩充局部变量表的访问索引
运算指令
概述
运算或算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。
分类
整形数据运算指令
浮点型数据运算指令
遵循IEEE754规范
注意:byte、short、char、boolean类型的算术指令,对于这类数据的运算,应使用操作int类型的指令代替
异常
只有除法指令(idiv和ldiv)已经求余指令(irem和lrem)中当出现除数为零时会导致虚拟机抛出ArithmeticException异常,其余仍和整数型数运算场景都不应该抛出运行时异常
类型转换指令
概述
种类
宽化类型转换
概述
虚拟机直接支持,比如int型转为long型,即小范围类型向大范围类型的安全转换
常见种类
int->long、float、double
long->float、double
float->double
窄化类型转换
可能丢失精度,比如将long型转换为int型
对象创建与访问指令
对象创建
创建实例
new
创建数组
newarray
anewarray
multianewarray
访问指令
访问类字段
static字段,或者称为类变量
getstatic
putstatic
实例字段
非static字段,或者称为实例变量
getfield
putfield
把一个数组元素加载到操作数栈
将一个操作数栈的值存储到数组元素中
取数组长度的指令
检查类实例类型的指令
操作数栈管理指令
将操作数栈的栈顶一个或两个元素出栈
pop
pop2
复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶
将栈最顶端的两个数值互换
swap
控制转移指令
概述
分类
条件分支
复合条件分支
无条件分支
方法调用和返回指令
方法调用
invokevirtual
用于调用对象的实例方法
invokeinterface
用于调用接口方法
invokespecial
用于调用一些需求特殊处理的实例方法
invokestatic
用于调用类方法(static方法)
invokedynamic
用于再运行时动态解析出调用点限定符所引用的方法
异常处理指令
同步指令
类的加载机制
概述
什么是加载机制?
类的生命周期
加载
概述
流程
通过一个类的全限定名来获取定义此类的二进制字节流
来源
Jar、EAR、WAR
网络(Applet)
运行时生成
由其他文件生成
数据库中读取
类型
非数组类
通过loadClass()方法(可以通过重写的方式自定义此操作)
数组类
Element type
Component Type
将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
连接
验证
概述
验证流程
文件格式验证
元数据验证
字节码验证
符号引用验证
java.lang.IncompatibleClassChangeError
IllegalAccessError
NoSuchFieldError
NoSuchMethodError
jvm参数
-Xverify:none
关闭大部分的类验证措施,以缩短虚拟机类加载的时间
准备
概述
解析
概述
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
符号引用
符号引用以一组符号来描述引用的目标,符号可以使任何形式的字面量,只要使用时能够无歧义的定位到目标即可。
直接引用
直接引用可以使直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
多次引用
动态引用
流程
类或接口的解析
字段解析
类方法解析
接口方法解析
初始化
概述
到了初始化阶段,才真正开始执行类中定义的Java程序代码。
初始化阶段是执行类构造器()方法的过程
非法向前引用变量
种类
类
初始化条件
是
有且只有五种
否
被动引用
通过子类引用父类的静态字段,不会导致子类初始化
通过数组定义来引用类,不会触发此类的初始化
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类初始化
初始化关键字
new
getstatic
putstatic
invokestatic
接口
使用
卸载
类加载器
概述
分类
虚拟机角度
启动类加载器(Bootstrap ClassLoader)
HotSpot虚拟机是C++语言实现,是虚拟机的一部分
这个类加载器负责将存放在\lib目录中,或者被-Xbootclasspath参数所指定的路径中
的,并且是虚拟机识别的类库加载到虚拟机内存中。
启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给
引导加载器,那直接使用null代替即可
所有其它的类加载器
Java语言实现,独立于虚拟机外部,并且全部继承自抽象类
开发者角度
启动类加载器
扩展类加载器
负责加载\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接用扩展类加载器
应用程序加载器
由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统加载器。
他负责加载用户路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序没有自定义过自己的类加载器
一般情况下这个就是程序默认的类加载器
双亲委派模型(Parents Delegation Model)
概述
什么是双亲委派模型?
双亲委派模型要求除了顶层的启动类加载器外,其余的加载器都应该有自己的父类加载器
加载器之间的父子关系一般不会以继承的关系来实现,而是使用组合(指定或者委派)关系来服用父类加载器的代码
优点
如果没有双亲委派模型?
不能保证Object类的唯一性(基础类的统一性问题)
哪怕‘相同的两个类’,因为不是同一个类加载器加载的,最终的比对结果也是不同的
工作流程
模型实现
分支主题
分支主题
OSGi
执行引擎
概述
执行形式
解释执行
通过解释器执行
编译执行
通过即时编译期产生本地代码执行
两者兼备
方法调用
概述
方法调用并不等于方法执行,方法调用阶段唯一的任务就是确定调用哪个方法,不涉及到方法内部的具体运行过程。
流程
解析
方式
符号引用
直接引用(Resolution)
调用指令
非虚方法
invokestatic
调用静态方法
invokespecial
调用实例构造器方法、私有方法和父类方法
虚方法
invokevirtual
调用所有的虚方法
invokevirtual虽然能够调用final方法,但是final修饰的方法为非虚方法
invokeinterface
调用接口方法,会在运行时再确定是一个实现此接口的对象
invokedynamic
分派
静态分派
概述
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,典型应用的是方法重载。
静态分配发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
分类
静态类型(外观类型)
实际类型
方法重载
高效并发
线程安全与锁优化
线程安全
分类
不可变
比如用final修饰的变量
绝对线程安全
比如Vector类
相对线程安全
线程兼容
线程独立
实现方法
阻塞式(悲观锁)
互斥同步
synchronized
概述
非公平锁
monitorenter
执行的时候,首先要获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1。
如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。
monitorexit
执行的时候,会将锁计数器减1,当计数器为0时,锁就被释放。
concurrent(ReentrantLock)
高级特性
等待可中断
可实现公平锁
锁可以绑定多个条件
概述
默认为非公平锁
非阻塞(乐观锁)
CAS(Compare And Swap)
锁优化
自旋锁与自适应自旋
锁消除
锁粗化
轻量级锁
偏向锁
0 条评论
下一页