JVM详解
1.JDK,JRE和JVM的关系
JDK: JAVA开发工具: 顾名思义 —> 用JAVA开发的时候要用到的工具 (javac.exe,java.exe)
JRE: JAVA运行环境: 顾名思义 —> 拿到工具了可不行,你必须有材料 —> JAVA开发所需要的基础包 (e.g:rt.jar) 里面有基础的类,String类等
JVM: JAVA虚拟机: 顾名思义 —> 工具有了,材料有了,就差一个工人 —> 一台虚拟的计算机这台计算机会将写的代码执行起来 —> 但是这个计算机么有图形操作界面,是不可以看见的,但是可以使用终端来对其操作 —>
终端: 就是控制计算机的一个面板,如cmd
图形解释:
执行的过程
注意事项: JAVA是跨平台的语言,JVM是跨语言的平台
怎么理解上述两句话:
1.JAVA是跨平台的语言
什么是windows操作系统的计算机 —> CPU只认识Windows给的数据 —> 比如你输入了你好 —> windows就会给你变成二进制 0101000传给CPU --> CPU就会知道0101000是你好,给用户返回你好
什么是Linux操作系统的计算机 —> CPU只认识Linux给的数据 —> 比如你输入了你好 —> Linux就会给你变成二进制 1010111 传给CPU --> CPU就会知道1010111是你好,给用户返回你好
但是 JVM可以根据操作系统的不同将你好转变为该系统认识的指令
所以只要将 .java文件编译成.class文件给JVM处理就会在不同的平台显示相同的数据
2.JVM是跨语言的平台
JVM只识别.class文件,但是不同的语言都可以编译成class文件,比如go语言通过一定的编译就可以变成 .class文件,这样传给虚拟机都可以显示想要的数据
2.JVM的架构模型
看看物理机,寄存器,栈的相对位置
1.基于栈的架构
首先什么叫做基于栈的架构 —> 意思就是:围绕栈设计
1.让栈在内存中怎么开辟空间
2.栈在内存中开辟的大小怎么设计
等等 …
优点:
1.实现简单,适用于资源受限的设备 --> 实现:就是给内存条里面开内存,资源受限的设备–>比如机顶盒数据寄存器如果只有一个,但是对于机顶盒的寄存器也是它那一套的取法, 如果取数据还必须要三个数据寄存器就无法取出数据了
2.避免了寄存器的分配问题 —> 有很多寄存器但是每个寄存器的功能不一样,有的寄存器只执行指令后面有操作数,或者操作地址的指令,但是现在不需要分配了,JVM的指令就是0地址,也不用给那种只操作地址的寄存器了
3.指令集少,方便
比如就几百条指令,但是正常的指令可能会有上万条,也方便记忆
4.不需要硬件,方便迁移
就是在不同的操作系统上,只需要开辟内存里面的空间 —> 不需要硬件支持
在不同的操作系统上都可以下载 —> 方便迁移,意思就是方便来回在不同平台下载
2.基于寄存器的架构
首先什么叫做基于寄存的架构 —> 意思就是: 围绕寄存器来设计
1.让寄存器怎么工作更加快速找数据
2.需要几个寄存器才可以使用JVM
等等 …
优点:
1.查找速度快
把数据存放在内存不同的地方都可以快速找到
2.指令少,指令集多
比如 add 这条指令 就代表了 把第一个数取出来,然后再把第二个数取出来,然后两个数相加
指令集多 —> 就是 add …等几万条这种指令
3.JVM的架构采取基于栈的架构实现
原因:
1.跨平台性
2.基于寄存器的设计比较麻烦
3.基于寄存器设计跨平台性就比较差了
3.JVM的生命周期
1.JVM的启动
概念: 当用javac.exe将.java编译成.class,在使用java.exe来启动.class的时候,JVM就启动了,就相当于点击java.exe JVM就开启了
点击 java.exe --> 即使开启么有加载文件JVM也不会执行
2.JVM的执行
概念: 当开始加载你的.class文件的时侯,就开始运行了
加载 .class文件
3.JVM的结束
1.当class文件正常运行完成
2.class文件出错,比如出现异常,但是抓住异常不算出错
3.使用Java方法来直接结束 —> System.exit(); runtime.halt()
4.操作系统出错,导致java.exe直接停止
5.本地操作系统使用它的方法让程序停止
4.图形解释
4.类的加载过程
1.加载(Loading)
概念: 将本地或者网上的 .class 文件加载进来(加载过程是双亲委派机制)
1.通过类的完全限定类名(一个JVM中完全限定类名是唯一的),将这个.class文件以二进制流的形式加载进JVM内存中
2.加载进来会在内存中生成一个代表此类的class对象,这个对象就是以后创建对应此类实例的模板
2.链接(Linking)
概念: 验证–> 准备 --> 解析 统一称为链接
1.验证:
概念:检查class文件是否满足JVM规范
比如 —> 字节流文件的开头必须是什么 (CA FE BA BE)
2.准备:
概念:对class对象进行默认化设置
**对概念的理解:**因为现在是对class对象进行准备 --> 其实也就可以认为是对类对象进行准备 —> 类对象对应的就是类成员变量 进行准备
即给static修饰的成员进行默认化设置
可以通过对class文件进行反解释 —>
反编译: 就是将class文件变成java文件
反解释: 就是对class文件(流文件) 进行解释
工具: Jclasslib —> 直接在idea上安装插件即可
如图所示: 这个就是将class文件解释了,其中可以看出有一个 < clinit > 方法 , 这个就是类初始化的方法
比如:
private static int i = 10;
先对类变量进行默认话设置 —> 然后等初始化的时候在初始化
即: int 类型 所以先给 i = 0;
其他类型的默认值: byte=0,short=0,int=0,char=’\u0000’,long=0,float=0.0,double=0.0,boolean=false
引用数据类型是null
注意事项:
1.只给类变量来设置默认值
2.如果用final来定义类变量必须给赋值(就不用默认化,直接就将值给类变量)
3.不给实例变量赋默认值
注意事项是在准备这个时期做的事情
3.解析:
概念:将符号引用变成直接引用
- 符号引用:
概念: 以一组符号来描述所引用目标,符号引用的字面量形式明确定义在《Java虚拟机规范》的class文件中了
如图所示:符号引用
- 直接引用:
概念: 就是一个明确地址
符号引用举例:
#1 ----> 转换成 Object的构造方法的地址
注意:这里将的是静态分析,动态链接在后面
两者共同点
都是用连将符号引用转换为直接引用的 —> 例如将 #1 ----> 转换成 Object的构造方法的地址
两者的区别
解析时期的不同:
1.静态解析: 在类加载阶段,或者第一次使用 —> 非虚方法的地址在这个时期加载 --> 非虚方法:static,private,final修饰的方法和,super.方法名,构造方法,后面会具体讲解非虚方法 —> 分析一次就好
2.动态链接: 在每一次运行期间 —> 虚方法在这个时期加载 --> 每一次执行都需要将 对应的符号引用变成直接引用
为什么说每一次运行期间?
代码说明
public class StaticAndDynamic {
public static void main(String[] args) {
StaticAndDynamic staticAndDynamic = new StaticAndDynamic();
//线程一使用method() ---> 第一次 ---> 会把method地址解析一次
Thread dog = new Thread(new Runnable() {
@Override
public void run() {
staticAndDynamic.method();
System.out.println(Thread.currentThread().getName()+":使用一次method");
}
}, "线程一");
//线程二使用method() ---> 第二次 --> 会把method地址解析一次
Thread cat = new Thread(new Runnable() {
@Override
public void run() {
staticAndDynamic.method();
System.out.println(Thread.currentThread().getName()+":使用一次method");
}
}, "线程二");
dog.start();
cat.start();
}
public void method(){
}
}
直接引用举例
如下代码进行解释
public class Parsing {
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
}
}
解释结果如下: —> javap -v Parsing.class
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1 //后面么 #为头的符号引用
7: iload_2
8: iadd
9: istore_3
10: return
LineNumberTable:
line 9: 0
line 10: 3
line 11: 6
line 14: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
3 8 1 i I
6 5 2 j I
10 1 3 k I
}
如上图这几条指令:
6: iload_1
7: iload_2
指令的意思就是将局部变量表里面索引为1和2的局部变量地址压入操作数栈中
直接将索引为1的局部变量地址压入操作数栈 —> 不需要将"#… —> 解析成对应的地址"
3.初始化(initialization)
概念: 初始化类成员变量 —> 初始化本类的时候先会初始化父类
上图是么写构造方法 --> 但是解释器有 < init > 这个方法
使用 < clinit >方法进行初始化,如上图所示在java反解释的图形化界面就可以看出—>
他是从.class文件里解释出来的, 那么意思就是java代码中应该有这个方法 —>
但是实际代码是么有这个方法的 —> 而且按照方法的加载顺序来看它应该是最后一个加载进来的 -->
那么就可以认为是一个隐藏的方法 —>
但是它在初始化的时候生效,作用就是给类成员变量初始化
private static int num = 10;
static{
num = 20;
}
javac会将 类成员和 静态代码块里 的代码全部拿出来, 然后在这个阶段使用 < clinit > 这个方法执行, 并且是按照代码顺序来执行的
注意1:非法前向引用
public class Init extends InitFather{
//证明类构造器方法只初始化 类成员变量
private static int num = 10;
private int num2 = 10;
static{
num = 20;
System.out.println(num);
System.