由于之前看的容易忘记,因此特记录下来,以便学习总结与更好理解,该系列博文也是第一次记录,所有有好多不完善之处请见谅与留言指出,如果有幸大家看到该博文,希望报以参考目的看浏览,如有错误之处,谢谢大家指出与留言。
目录:
JVM的概念
JVM发展历史
JVM种类
Java语言规范
JVM规范
一、初识JVM – JVM概念
1、JVM是Java Virtual Machine的简称。意为Java虚拟机
2、虚拟机:指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统
-VMWare
-Visual Box
-JVM
4、不同虚拟机比较
VMWare或者Visual Box与JVM不同之处他们都是使用软件模拟物理CPU的指令集,所模拟的对象都是真实存在的计算机,所模拟的cpu都是现实可找到的CPU案例,包括CPU,硬盘,内存等,JVM模拟的对象是现实找不到的,并没有真实计算机去运行执行字节码,单纯从软件去做的设计。在正常的CPU中,都有若干个寄存器,而jvm的cpu中除了PC寄存器外,其他的JVM本身对其他寄存器做了裁剪,因为他是纯粹软件去实现的,所以寄存器效果并没有多大作用,引入而且会对JVM设计有运行带来大量的麻烦,所以就去掉了。
5、JVM使用软件模拟Java 字节码的指令集,与平时说的CPU指令集并不一样
二、初识JVM —— Java和JVM的历史
1、1996年 SUN JDK 1.0 Classic VM
— 纯解释运行,使用外挂进行JIT (这个性能是非常低的,所以开始让人们对java感觉性能非常低)
2、1997年 JDK1.1 发布— AWT、内部类、JDBC、RMI、反射
3、1998年 JDK1.2 Solaris Exact VM(这个虚拟机寿命比较短,由于hsotspot的加入导致他淘汰,但JDK1.2开始 称为Java 2;J2SE J2EE J2ME 的出现加入Swing Collections)(1)、JIT 解释器混合
(2)、Accurate Memory Management 虽然淘汰但他却做到精确内存管理,数据类型敏感
(3)、提升的GC性能
4、2000年 JDK 1.3 Hotspot 作为默认虚拟机发布 (也加入(关与声音一些API)JavaSound)5、2002年 JDK 1.4 (最早的虚拟机)Classic VM退出历史舞台 同时JDK1.4也加入Assert 正则表达式 NIO IPV6 日志API 加密类库 这时候java类库就相当完整了。
6、2004年发布 JDK1.5 即 JDK5 、J2SE 5 、Java 5 (这个版本对java至关重要的)
加入了如下内容:
— 泛型 — 注解 — 装箱 — 枚举 — 可变长的参数 — Foreach循环7、JDK1.6 JDK6加入如下内容:
— 脚本语言支持 — JDBC 4.0 — Java编译器 API
8、2011年 JDK7发布(由于在版本规划中加入太多东西,一度难以发布,所以好多原本计划的内容推到jdk1.8中)
— 延误项目推出到JDK8 — G1 — 动态语言增强 — 64位系统中的压缩指针 — NIO 2.0
9、2014年 JDK8发布
— Lambda表达式 — 语法增强 Java类型注解
10、2016年JDK9— 模块化
三、初识JVM—Java和JVM的历史 —— 发展中的大事件
1、使用最为广泛的JVM为HotSpot
2、HotSpot 为Longview Technologies开发 被SUN收购
3、2006年 Java开源 并建立OpenJDK— HotSpot 成为Sun JDK和OpenJDK中所带的虚拟机
4、2008 年 Oracle收购BEA— 得到JRockit VM
5、2010年Oracle 收购 Sun— 得到Hotspot 从此,Oracle拥有了两个主流的虚拟机JRockit和Hotspot
6、Oracle宣布在JDK8时整合JRockit和Hotspot,优势互补— 在Hotspot基础上,移植JRockit优秀特性 (在JDK1.8把他俩整合,因为Oracle不可能维护两个虚拟机,所以把他们整合)
四、初识JVM —— 各式JVM
1、KVM
-- SUN发布-- IOS Android前,广泛用于手机系统(以前java也是应用于手机)
2、CDC/CLDC HotSpot
-- 手机、电子书、PDA等设备上建立统一的Java编程接口
-- J2ME的重要组成部分
3、JRockit-- BEA
4、IBM 的J9 VM
-- 用于IBM内部程序,自己用。
5、Apache Harmony
-- 兼容于JDK 1.5和JDK 1.6的Java程序运行平台
-- 与Oracle关系恶劣 阿帕奇退出JCP ,Java社区的分裂
-- OpenJDK出现后,受到挑战 2011年 退役
-- 没有大规模商用经历
-- 对Android的发展有积极作用
五、初识JVM – 规范
(一)java规范
-- 语法
-- 变量
-- 类型
-- 文法
1、语法定义
-- If ThenStatement:
if ( Expression ) Statement
-- ArgumentList:支持多个参数列表Argument
ArgumentList , Argument
2、词法结构
-- \u + 4个16进制数字 表示UTF-16
-- 行终结符: CR, or LF, or CR LF.
-- 空白符
-- 空格 tab \t 换页 \f 行终结符
-- 注释
-- 标识符
-- 关键字
标识符:标识字符但不是关键字或布尔值文字或零文字
IdentifierChars: JavaLetter IdentifierChars JavaLetterOrDigit
JavaLetter: any Unicode character that is a Java letter (see below)
JavaLetterOrDigit: any Unicode character that is a Java letter-or-digit (see below)
例子:
public static void 打印(){
System.out.println("中文方法哦");
}
public static void main(String[] args) {
打印();
}
— Int
0 2 0372 0xDada_Cafe 1996 0x00_FF__00_FF
— Long
0l 0777L 0x100000000L 2_147_483_648L 0xC0B0L
— Float
1e1f 2.f .3f 0f 3.14f 6.022137e+23f
— Double
1e1 2. .3 0.0 3.14 1e-9d 1e137
— 操作
+= -= *= /= &= |= ^= %= <<= >>= >>>=
4、类型和变量
— 元类型
byte short int long float char
— 变量初始值
boolean false
char \u0000
— 泛型
举例:
class Value { int val; }
class Test {
public static void main(String[] args) {
int i1 = 3;
int i2 = i1;
i2 = 4;
System.out.print("i1==" + i1);
System.out.println(" but i2==" + i2);
Value v1 = new Value();
v1.val = 5;
Value v2 = v1;
v2.val = 6;
System.out.print("v1.val==" + v1.val);
System.out.println(" and v2.val==" + v2.val);
}
}
结果:i1==3 but i2==4
v1.val==6 and v2.val==6
i1 i2为不同的变量
v1 v2为引用同一个实例
5、还有其他一些规范
— Java内存模型
— 类加载链接的过程
— public static final abstract的定义
— 异常
— 数组的使用
— …….Java语言规范定义了什么是Java语言,则
JVM规范定义了什么是JVM,所以java语言与jvm是相对独立的,其他语言也可以运行在JVM中,只要准守jvm规范
(二)JVM规范
-- Class文件类型
-- 运行时数据
-- 帧栈
-- 虚拟机的启动
-- 虚拟机的指令集
1、Java语言和JVM相对独立。其他语言也可以运行在JVM比如:
(1)、 Groovy (2)、 Clojure (3)、Scala
2、JVM主要定义二进制class文件和JVM指令集等
3、Class 文件格式
4、数字的内部表示和存储
— Byte -128 to 127 (-27 to 27 - 1)
5、returnAddress 数据类型定义
— 指向操作码的指针。不对应Java数据类型,不能在运行时修改。Finally实现需要/
6、定义PC
7、堆
8、栈
9、方法区
10、JVM中整数的表达
— 原码:第一位为符号位(0为正数,1为负数)
— 反码:符号位不动,原码取反
— 负数补码:符号位不动,反码加1
— 正数补码:和原码相同
— 打印整数的二进制表示
int a=-6;
for(int i=0;i<32;i++){ //因为整数有32位,所以按位取时,做32次循环,每一次取一位
int t=(a & 0x80000000>>>i)>>>(31-i); //0x80000000最高位为1,a & 0x80000000 就是把a的第一位取出来,a做为无符号的右移>>>(31-i) 这个多位,就打印出这个数字的2进制表示。
System.out.print(t); }
为什么要用补码?好处(1)因为0无正负,难以表示,所以用补码表示计算才是正确的,(2)补码能够很好的参与计算机的二进制计算。
(1)计算0的表示:
(2)举例:
11、Float的表示与定义
— 支持 IEEE 754
> s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
以单精度为例:
举例:
> s*m*2^(e-127)
12、一些特殊的方法
<clinit> 类的初始化方法
<init> 实例测初始化方法
在虚拟机中构造,比如类如果我们没有初始化构造函数,他会默认帮那个我们初始化
13、JVM指令集
— 类型转化
l2i
— 出栈入栈操作
aload astore
— 运算
iadd isub
— 流程控制
ifeq ifne
— 函数调用invokevirtual invokeinterface invokespecial invokestatic
14、JVM需要对Java (库)Library 提供以下支持:
— 反射 java.lang.reflect
— ClassLoader 类装载器。java中是有加载器,但最终启动是这个,在虚拟机中,而且java是无法操作它的。
— 初始化class和interface
— 安全相关 java.security
— 多线程
— 弱引用
15、JVM的编译
— 源码到JVM指令的对应格式
— Javap
— JVM反汇编的格式<index> <opcode> [ <operand1> [ <operand2>... ]] [<comment>]
举例:
void spin() {
int i;
for (i = 0; i < 100; i++) { ;
// Loop body is empty
}
}
虚拟机中直接执行的事下面这些指令代码:
0 iconst_0 // Push int constant 0 首先操作0,所以把零入栈
1 istore_1 // Store into local variable 1 (i=0) 存储局部变量1
2 goto 8 // First time through don't increment 第一次通过不增加
5 iinc 1 1 // Increment local variable 1 by 1 (i++) 增量局部变量1
8 iload_1 // Push local variable 1 (i) 推局部变量1入栈
9 bipush 100 // Push int constant 100 再把常量100入栈
11 if_icmplt 5 // Compare and loop if less than (i < 100) 比较和循环如果小于
14 return // Return void when done 完成时返回
在例如:
4.public static int add(int a, int b) {
5.int c = 0;
6. c = a + b;
7. return c;
8.}
查看字节码的命令:javap -verbose ByteCode.class
add方法的字节码如下:
public static int add(int, int);
descriptor: (II)I //描述方法参数为两个int类型的变量和方法的返回类型是int的
flags: ACC_PUBLIC, ACC_STATIC //修饰方法public和static
Code:
stack=2, locals=3, args_size=2 //操作数栈深度为2,本地变量表容量为3,参数个数为2
0: iconst_0 //将int值0压栈
1: istore_2 //将int值0出栈,存储到第三个局部变量(slot)中
2: iload_0 //将局部变量表中第一个变量10压栈
3: iload_1 //将局部变量表中第一个变量20压栈
4: iadd //将操作数栈顶两个int数弹出,相加后再压入栈中
5: istore_2 //将栈顶的int数(30)弹出,存储到第三个局部变量(slot)中
6: iload_2 //将局部变量表中第三个变量压栈
7: ireturn //返回栈中数字30
LineNumberTable:
line 5: 0 //代码第5行对应字节码第0行
line 6: 2 //代码第6行对应字节码第2行
line 7: 6 //代码第7行对应字节码第6行
LocalVariableTable:
Start Length Slot Name Si
0 8 0 a I //a占用第1个solt
0 8 1 b I //b占用第2个solt
2 6 2 c I //c占用第3个solt
局部变量表:存放的一组变量的存储空间。存放方法参数和方法内部定义的局部变量表。
在java编译成class的时候,已经确定了局部变量表所需分配的最大容量。
局部变量表的最小单位是一个Slot。
虚拟机规范没有明确规定一个Slot占多少大小。只是规定,它可以放下boolean,byte,...reference &return address.
reference 是指一个对象实例的引用。关于reference的大小,目前没有明确的指定大小。但是我们可以理解为它就是类似C++中的指针。
局部变量表的读取方式是索引,从0开始。所以局部变量表可以简单理解为就是一个表.
局部变量表的分配顺序如下:
this 引用。可以认为是隐式参数。
方法的参数表。
根据局部变量顺序,分配Solt。
一个变量一个solt,64为的占2个solt。java中明确64位的是long & double
为了尽可能的节约局部变量表,Solt可以重用。
注意:局部变量只给予分配的内存,没有class对象的准备阶段,所以局部变量在使用前,必须先赋值。
局部变量表可参考:https://www.tuicool.com/articles/URZrMnb
根据上面字节码画出下面局部变量表和操作数栈之间的操作关系。
图中调用add(10,20)传入的参数是a=10;b=20。
- 指令0执行后:局部变量表中有两个数字10、和20,操作数栈一个值0,程序计数器指向第0行字节码指令
0: iconst_0 //将int值0压栈 - 指令1执行后:局部变量表中有三个数字10、20和0,操作数栈没有值,程序计数器指向第1行字节码指令
1: istore_2 //将int值0出栈,存储到第三个局部变量(slot)中 - 指令2执行后:局部变量表中有三个数字10、20和0,操作数栈一个值10,程序计数器指向第2行字节码指令
2: iload_0 //将局部变量表中第一个变量10压栈 - 指令3执行后:局部变量表中有三个数字10、20和0,操作数栈两个值10和20,程序计数器指向第3行字节码指令
3: iload_1 //将局部变量表中第一个变量20压栈 - 指令4执行后:局部变量表中有三个数字10、20和0,操作数栈一个值30,程序计数器指向第4行字节码指令
4: iadd //将操作数栈顶两个int数弹出10和20,相加后再压入栈中 - 指令5执行后:局部变量表中有三个数字10、20和30,操作数栈没有值,程序计数器指向第5行字节码指令
5: istore_2 //将栈顶的int数(30)弹出,存储到第三个局部变量(slot)中 - 指令6执行后:局部变量表中有三个数字10、20和30,操作数栈一个值30,程序计数器指向第6行字节码指令
6: iload_2 //将局部变量表中第三个变量压栈 - 指令7执行后:将栈中的数字返回给调用方法,并销毁此栈帧
7: ireturn //返回栈中数字30
此篇只是介绍认识JVM以及规范,后续再单独继续深入。