文章目录
JVM的概念
JVM是Java Virtual Machine的简称。意为Java虚拟机
虚拟机
指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统
有哪些虚拟机
- VMWare
- Visual Box
- JVM
VMWare或者Visual Box都是使用软件模拟物理CPU的指令集
比如它们模拟的cpu、硬盘、内存,等都是现实存在的,能够在现实中找到对应的案例的。
JVM使用软件模拟Java 字节码的指令集
JVM模拟的对象是现实中没有的,现实中没有任何一个计算机能够运行Java字节码,单纯从从软件上做的一个设计,来模拟一个硬件的行为,比如jvm运行的的是java字节码指令集,为了设计上的精简,我们知道正常的cpu中有若干个寄存器,jvm中除了pc寄存器外,其他的寄存器都做了裁剪,因为寄存器的主要功能是加快数据访问的速度,因为在jvm中是纯粹用软件模拟的,速度使用寄存器后并不会有很好的提升,并且引入寄存器后,也为jvm的设计以及实现带来很大的困难,所以把这些功能去掉了。综上,jvm是一个被定制过的现实中不存在的计算机。
JVM发展历史
1996年 SUN JDK 1.0 Classic VM
纯解释运行,使用外挂进行JIT,没有内置的即时编译的模块,一旦开启外挂,解释运行的功能就没有了。
Classic VM生命周期比较长,在1.3 、1.4的时候才被淘汰
1997年 JDK1.1 发布
-AWT、内部类、JDBC、RMI、反射
1998年 JDK1.2 Solaris Exact VM
JIT 解释器混合
Accurate Memory Management 精确内存管理,数据类型敏感
提升的GC性能
JDK1.2开始 称为Java 2
J2SE J2EE J2ME 的出现
加入Swing Collections
Solaris Exact VM生命周期比较短。
2000年 JDK 1.3 Hotspot 作为默认虚拟机发布
加入JavaSound加入了一些声音上的api
2002年 JDK 1.4 Classic VM退出历史舞台
Assert 正则表达式 NIO IPV6 日志API 加密类库
2004年发布 JDK1.5 即 JDK5 、J2SE 5 、Java 5(重要版本)
泛型
注解
装箱
枚举
可变长的参数
Foreach循环
JDK1.6 JDK6
脚本语言支持
JDBC 4.0
Java编译器 API(开放了这个API)
2011年 JDK7发布
延误项目推出到JDK8
G1(全新的gc收集器)
动态语言增强(动态语言的火热)
64位系统中的压缩指针
NIO 2.0
2014年 JDK8发布(重要版本)
Lambda表达式(模拟了函数式编程)
语法增强 Java类型注解
2016年JDK9
模块化
大事件
使用最为广泛的JVM为HotSpot
HotSpot 为Longview Technologies开发 被SUN收购
2006年 Java开源 并建立OpenJDK
HotSpot 成为Sun JDK和OpenJDK中所带的虚拟机
2008 年 Oracle收购BEA
得到JRockit VM
2010年Oracle 收购 Sun
得到Hotspot
Oracle宣布在JDK8时整合JRockit和Hotspot,优势互补
在Hotspot基础上,移植JRockit优秀特性
推测:JRockit在不久的将来会退出历史舞台,但是HotSpot会深受JRockit的影响。
JVM种类
KVM
SUN发布
IOS Android前,广泛用于手机系统
CDC/CLDC HotSpot
手机、电子书、PDA等设备上建立统一的Java编程接口
J2ME的重要组成部分
JRockit
IBM J9 VM
IBM内部
Apache Harmony
兼容于JDK 1.5和JDK 1.6的Java程序运行平台
与Oracle关系恶劣 退出JCP ,Java社区的分裂
OpenJDK出现后,受到挑战 2011年 退役
没有大规模商用经历
对Android的发展有积极作用
BEA
Java语言规范
语法定义
IfThenStatement:
if ( Expression ) Statement
if(true){do sth;}
ArgumentList:
Argument
ArgumentList , Argument
add(a,b,c,d);
词法结构
\u + 4个16进制数字 表示UTF-16
行终结符: CR, or LF, or CR LF.
空白符
空格 tab \t 换页 \f 行终结符
注释
标识符
关键字
//标识符:标识符字符串不能使关键字或者布尔值或者null
Identifier: IdentifierChars but not a Keyword or BooleanLiteral or NullLiteral
//标识符串:java字符
IdentifierChars: JavaLetter IdentifierChars JavaLetterOrDigit
//java字符:任何一个unicode
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) {
打印();
}
有意思的是,代码中有中文
数字相关
在jdk1.7中一个比较大的改变是允许有下划线,好处是当数字比较大的时候看的比较清楚一些。
- 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 - 操作
+= -= *= /= &= |= ^= %= <<= >>= >>>=
哪些是合法的数字呢?
private int a=0xDada_Cafe;
private float b=0x1.fffffeP+127f;
private float c=1996;
private float d=1996.3;
private int f=9999e2;
private double g=33e2;
private float h=0x1.fffep-12f;
private float i=1.fffep-12f;
private long p=0b1_1_1_0_1;
private long q=0b1_1_1_0_2;
类型和变量
- 元类型
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);
}
}
i13 but i24
v1.val6 and v2.val6
i1 i2为不同的变量
v1 v2为引用同一个实例
略
Java内存模型
类加载链接的过程
public static final abstract的定义
异常
数组的使用
…….
JVM规范
Java语言规范定义了什么是Java语言
Java语言和JVM相对独立
符合JVM规范的就能够在JVM上运行,比如:
-
Groovy
-
Clojure
-
Scala
JVM主要定义二进制class文件和JVM指令集等 -
Class 文件格式
-
数字的内部表示和存储
Byte -128 to 127 (-27 to 27 - 1) -
returnAddress 数据类型定义
指向操作码的指针。不对应Java数据类型,不能在运行时修改。Finally实现需要 -
定义PC
-
堆
-
栈
-
方法区
整数的表达
- 原码:第一位为符号位(0为正数,1为负数)
- 反码:符号位不动,原码取反
- 负数补码:符号位不动,反码加1
- 正数补码:和原码相同
- 打印整数的二进制表示
int a=-6;
//因为整数有32位,所以进行32次循环。
for(int i=0;i<32;i++){
//0x80000000表示最高位为1的数字,所以a & 0x80000000只有一位是1,
//第一次i=0,就是把a的第一位取出来,无符号右移(31-i)位。
//依次循环,将每一位打印出来
int t=(a & 0x80000000>>>i)>>>(31-i);
System.out.print(t);
}
5
00000101
-6
原码: 10000110
反码: 11111001
补码: 11111010
-1
原码: 10000001
反码: 11111110
补码: 11111111
为什么要用补码?
0是一个比较特殊的数字,既不属于正数,也不属于负数
计算0的表示:
0
正数:00000000
负数:10000000
0
正数:00000000
0
负数:10000000
反码:11111111
补码:00000000
看到这里补码的好处是为了没有歧义的表示零
-6+5
11111010
+ 00000101
= 11111111
-4+5
11111100
+ 00000101
= 00000001
-3+5
11111101
+ 00000101
= 00000010
看到这里,补码还有一个好处就是方便的计算两个数字之和(两个补码之和,符号位直接参与运算,得到的记过就是正确的结果。如果是源码做计算,符号位参与运算得不到正确的结果。),当然减法是一个特殊的加法。
Float的表示与定义
支持 IEEE 754
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
s符号位 eeeeeeee 指数:8 mmmmmmmmmmmmmmmmmmmmmmm 尾数:23
e全0 尾数附加位为0 否则尾数附加位为1,所以这里的尾数看似是23位,实质上是24位。
表达式
s*m*2^(e-127)
m指的是后面的23位
例子
-5
1 10000001 01000000000000000000000
-1*2^(129-127)*(2^0+2^-2)
一些特殊的方法
clinit 类的初始化方法
init 实例的初始化方法
JVM指令集
- 类型转化
l2i - 出栈入栈操作
因为虚拟机没有寄存器,所以很多的指令都是通过栈的方式来操作的,所以有一系列的指令来定义出栈和入栈的操作。
aload astore - 运算
iadd(+) isub(-) - 流程控制
ifeq(相等) ifne(不相等) - 函数调用
invokevirtual(调用虚函数) invokeinterface (调用接口) invokespecial invokestatic (调用静态)
JVM需要对Java Library 提供以下支持:
因为这些功能没有办法通过java语言本身支持,所以通过JVM实现。
- 反射 java.lang.reflect
- ClassLoader
- 初始化class和interface
- 安全相关 java.security
- 多线程
- 弱引用
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
1 istore_1 // Store into local variable 1 (i=0)
2 goto 8 // First time through don't increment
5 iinc 1 1 // Increment local variable 1 by 1 (i++)
8 iload_1 // Push local variable 1 (i)
9 bipush 100 // Push int constant 100
11 if_icmplt 5 // Compare and loop if less than (i < 100)
14 return // Return void when done
在JVM中直接执行的是JVM指令代码(如上)。