知识储备:
JVM和普通虚拟机
vmvare,visualbox虚拟机:完整的一个能够提供虚拟主机的PC,所以我们需要在上边安装操作系统,是通过使用操作系统软件模拟物理CPU的指令集
jvm:程序自己的独立运行环境,比如说:对战,寄存器,虚拟硬件架构,Java字节码指令集等
JVM/JDK/JRE关系
JDK : Java Development ToolKit(Java开发工具包)。JDK是整个JAVA的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。
JRE:Java Runtime Enviromental(java运行时环境)。也就是我们说的JAVA平台,所有的Java程序都要在JRE下才能运行。包括JVM和JAVA核心类库和支持文件。与JDK相比,它不包含开发工具——编译器、调试器和其它工具。
JVM:是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
JVM产品有哪些
HotSpot VM、J9 VM、Zing VM
JAVA文件加载到JVM的运行流程:
.java文件编译为.class文件,.class文件通过JVM运行。
类加载器用的是双亲委派模型;
类加载过程:
1.加载(Load)
取得类的二进制字节流,通过类的全限定名称(包名+类名)
把二进制字节流中静态存储结构转化为方法区数据结构
在内存中生成代表这个类的java.lang.Class对象,放到堆中
2.链接(Link)
2.1验证(检测)
验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。
文件格式的验证,
是否是class文件,当前class文件是否支持当前的虚拟机等等
元数据验证
对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合Java语法规范的元数据信息。
字节码验证
该阶段验证的主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。
符号(其实就是关键字)引用验证
对方法的引用是否合法,转成直接引用
**2.2准备**
为类的静态变量分配内存,并初始化默认值
int: 0
long: 0L
boolean:false
**2.3解析**
在一个Java类中会包含对其它类或接口的形式引用,包括它的父类、所实现的接口、方法的形式参数和返回值的Java类等。解析的过程就是确保这些被引用的类能被正确的找到。解析的过程可能会导致其它的Java类被加载。
常量池中的符号引用替换为直接引用(符号引用,可以理解为一个占位符)。
例如person.fun() 在编译期间, 并不知道Person类的实际内存地址,因此只能使用符号来表示:
例如com.tu.demo.Person,表示一个符号。当然jvm实际中是使用似于CONSTANT_Class_info的常量来表示的
解析阶段,就是把这些符号引用,转换成一个个的指针
3.初始化(Initialize)
初始化阶段,才真正开始执行类中定义的java程序代码(或者说是字节码)
通俗的讲,初始阶段是执行类构造器<clinit>()方法的过程。
<clinit>()不是构造器,在构造器之前执行
如果这个类还没有被加载和链接,那就先加载和链接
如果类存在直接的父类,先初始化直接父类
如果类中存在初始化语句,那就依次执行初始化语句
主动引用:
new,创建类的实例对象
反射(和new是一样的,反射在init的时候调用的就是new)
初始化子类必需要先要初始化父类
调用类的静态方法
访问类中的静态变量或者给静态变量赋值
jvm启动的时候的启动类必须要先初始化(常量不会对类的初始化产生影响)
被动引用:
除上述之外,所有引用类的方式都不会出发初始化,称为被动引用。
类的构造器<clint>:
只能写不能读
(可以读已经定义的, 未定义的不能读)**需要写代码举例子**
例如:
1.通过子类引用父类的静态字段,不会导致子类的初始化
2.通过数组定义来引用类,不会出发此类的初始化
3.常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,两个类在编译之后不再有联系,因此不会触发类的初始化。
JVM运行结构:
class字节码文件通过类装载器子系统进行装载,
类的加载只会加载一次
JVM运行时数据区域:
计数器:
当前线程执行的字节码的行号指示器,通过改变此指示器来选取下一个需要执行的字节码指令
特征
在线程创建时创建
每个线程拥有一个
指向下一条指令的地址
方法区
线程共享
存储:
类信息
常量
静态变量【jdk1.7之前存放在方法区】
方法字节码
VM栈/本地方法栈
局部变量-基础类型【jdk1.7String放在堆】,如果是引用类型,则引用被存在栈上,引用指向的对象放在堆
线程私有
方法在执行时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息
方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
局部变量表所需的内存空间在编译期间完成分配,而且分配多大的局部变量空间是完全确定的,在方法运行期间不会改变其大小
出栈后空间释放
Heap
线程共享
存储对象或数组(new 出来的对象)
堆划分
非静态变量
静态变量【jdk1.7及之后,静态变量和非静态变量(普通变量) 都存在堆中】
注意:
jdk1.7之前,静态变量存放在方法区中,非静态变量存放在堆中(在实例化堆的时候)
jdk1.7及之后,静态变量和非静态变量(普通变量) 都存在堆中
局部变量如果是基础类型,则在栈上(jdk1.7String放在堆),如果是引用类型,则引用被存在栈上,引用指向的对象放在堆
JVM内存模型:
valitale
线程间可见
syhcronize语义:
串行
指令重排
现象:同一个线程中只有赋值操作。
比如一下代码,a 和 sign的执行顺序没有一定的顺序,就会发生指令重排
int a = 0;
boolean sign = false;
new Thread(new Runable(){
a += 1;
sign = true;
});
解决方案:synchroize
参考资料:
http://java-mzd.iteye.com/blog/838514
https://zhuanlan.zhihu.com/p/25228545
http://blog.csdn.net/songkai320/article/details/51819046
http://blog.csdn.net/qq_25235807/article/details/61920877
https://www.zhihu.com/question/29125656
https://my.oschina.net/wanghongkai/blog/507649(Java类加载机制介绍比较详细且到位)
http://www.infoq.com/cn/articles/cf-Java-class-loader#idp_register