JVM
JVM是Java虚拟机,用来运行java程序,负责解释执行字节码文件
字节码可以由不同的编程语言通过编译获得(Kotlin,Jython……),只需要遵循JSR-292规范
JVM上可以用多语言混合编程(Kotlin……)
虚拟机
用软件模拟一台计算机,用来执行一系列虚拟的计算机指令。
虚拟机分为系统虚拟机与程序虚拟机
系统虚拟机:对物理计算机的仿真,提供一个可运行完整操作系统的软件平台
程序虚拟机:专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令称为Java字节码指令
JVM的整体结构
Class文件
类装载器子系统
运行时数据区:方法区,堆,Java栈,本地方法栈,程序计数器
执行引擎
本地方法接口
本地方法库
JVM的架构模型
指令集架构有基于栈的指令集架构与基于寄存器的指令集架构
java属于基于栈的指令集架构
基于栈的指令集架构:
1、设计和实现更简单,适用于资源受限的系统
2、避开寄存器的分配难题
3、指令流中的指令大部分是零地址指令,依赖操作栈。指令集更小(指令多)
4、不需要硬件支持,可移植性更好,更好实现跨平台
基于寄存器架构:
1、指令集为x86指令集:传统pc与android的Davlik虚拟机
2、指令集架构则完全依赖硬件,可移植性差
3、性能优异,执行效率更高
4、一个操作花费的指令少
5、指令集往往都是一地址指令或二地址指令或三地址指令
JVM的生命周期
虚拟机的启动
java虚拟机的启动是通过引导类加载器创建一个初始类来完成,这个类是由虚拟机的具体实现所指定
虚拟机的执行
一个运行中的Java虚拟机有一个清晰的任务:执行Java程序(字节码)
程序开始执行时JVM才运行,程序结束时它就停止
执行一个所谓的Java程序的时候,在操作系统上真真正正的在执行的进程是Java虚拟机,有Java虚拟机解释执行字节码文件
虚拟机的退出
程序正常执行结束
程序在执行过程中出现异常或错误
由于操作系统的错误导致退出
某线程调用了Runtime类或者System类的exit方法或Runtime类的halt方法
使用JNI(Java Native Interface)的API来加载或者卸载Java虚拟机时,Java虚拟机会退出
硬件层面的关闭
类加载器子系统作用
负责从文件系统或者网络中加载class文件,class文件开头有特定的标识(CAFE BABE)
ClassLoader只负责class文件的加载,能否执行取决于Execution(执行引擎)
加载的类信息被放入方法区的内存空间中,除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量
类加载过程
加载——>链接(验证,准备,解析)——>初始化
对于没有装载的类,会先由ClassLoader装载
被装载的类进行链接
后进行初始化
调用类中的方法
……
结束
具体实现:
加载:
1、通过一个类的全限定类名获取定义此类的二进制字节流
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
链接:
验证:
目的在于保证Class文件的字节流符合虚拟机要求,保证加载类的正确性
四种验证:文件格式,元数据,字节码,符号引用
准备:
为类变量分配内存并且设置该类变量的默认初始值,即零值
准备阶段值的初始化不包括静态常量(final与static修饰),因为final在编译的时候就会分配,准备阶段会显示初始化
这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中
解析:
将常量池内的符号引用转换为直接引用的过程(将具体的值写入,不再是由符号代替)
解析在JVM执行完初始化后再执行
符号引用用来指代目标对象,但没有附上对象的地址,直接引用就直接指向对象的地址
解析动作主要针对类或接口,字段,类方法,接口方法,方法类型。
初始化:
初始化阶段就是执行类构造器方法()的过程
此方法不需定义,javac编译器自动收集类中的所有类变量的复制动作和静态代码块中的语句合并而来
构造器方法中指令按语句在源文件中出现的顺序执行
()不同于类的构造器,只是用来初始化类
虚拟机必须保证一个类的()方法在多线程下被同步加锁
注:,clinit只有在类中存在对静态变量赋值的情况下,才会有clinit
类加载器
严格来说,类加载器分为两类
引导类加载器,非Java语言实现
自定义类加载器,所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器,扩展类加载器与系统类加载器都是自定义类加载器
用户自定义类是使用系统类加载器加载的
使用代码确认:
ClassLoader cl1 = ClassLoader.getSystemClassLoader();
ClassLoader cl2 = Test.class.getClassLoader();
System.out.println(cl1.equals(cl2));//结果为true
引导类加载器通过Java代码获取
系统的核心类库都是用引导类加载器加载
ClassLoader cl = String.class.getClassLoader();
System.out.println(cl);//null
引导类加载器(Bootstrap ClassLoader)
使用C/C++语言实现的,嵌套在JVM内部
用来加载Java的核心库,用于提供JVM自身需要的类
不继承ClassLoader,没有父加载器
加载扩张类和应用程序类加载器,并制定为他们的父类加载器
引导类加载器只加载包名为java、javax、sun等开头的类
扩展类加载器(Extension ClassLoader)
Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
派生于ClassLoader类
父类加载器为启动类加载器
从java.ext.dirs系统属性指定的目录下加载类库或从JDK的安装目录的jre/lib/ext子目录下加载类库(目录下用户自定义的类也可以加载)
系统类加载器(AppClassLoader)
java语言编写,由sum.misc.Launcher$AppClassLoader实现
派生于ClassLoader类
父类加载器为扩展类加载器
它负责加载环境变量或者系统属性path指定路径下的类库
该类加载是程序中默认的类加载器
可以通过ClassLoader.getSystemClassLoader获取
用户自定义类加载器
用处:
隔离加载器
修改类加载的方式
扩展加载源
防止源码泄露(对字节码加密,用自定义类加载器解密,防止源码被破解)
ClassLoader的常用方法及获取
常用方法
getParent(),获取父类加载器
loadClass(String name),加载名称为name的类,返回包含该类信息的Class类的实例
findClass(String name),查找名称为name的类,返回包含该类信息的Class类的实例
defineClass(String name, byte[] b, int off, int len),将字节数组b中的内容转换为一个Java类(可以看做该数组内存放的字节可以翻译为字节码)
获取ClassLoader的途径
//获取当前类的ClassLoader
clazz.getClassLoader();//clazz为Class类的实例
//获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader();
//获取系统的ClassLoader
ClassLoader.getSystemClassLoader();
//获取调用者的ClassLoader
DriverManager.getCallerClassLoader();
双亲委派机制
JVM按需加载class文件(需要时才会加载)
加载某个类的class文件时,JVM采用的是双亲委派模式,把请求该给父类处理(一种任务委派模式)
工作原理
1、一个类加载器收到类加载请求时,自己不会立即执行,而是会将请求委托给父类加载器去执行
2、如果父类加载器也存在父类加载器,在重复上述步骤,直至到达顶层
3、父类加载器可以完成该加载请求,则返回成功,不能则将请求交还给子类加载器
示例:
自定义一个java.lang的包,包内定义一个String类,类中执行main方法
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("hello world!");
}
}
/**
运行结果为:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
原因:
该类被加载时,被委派给引导类加载器,java.lang.String在该加载器中被指定为加载系统默认的字符串类String。导致main方法所在的类无法被正常加载
**/
优势:
避免类的重复加载
保护程序安全,防止核心类被篡改(沙箱安全机制)
两个Class对象相同要满足全类名一致,类的加载器一致
类的主动使用和被动使用
主动使用:会对类进行初始化
1、创建类的实例
2、访问某个类或接口的静态变量,或者对该静态变量赋值
3、调用类的静态方法
4、反射Class.forName(“com.demo.Test”)
5、初始化一个类的子类
6、Java虚拟机启动时需要加载的启动类
被动使用:除了上述情况的所有使用方法都为被动使用
两种方法都会对类进行加载,但被动使用不一定会初始化