1、两种指令架构:栈式(只能在内存中进行指令操作)与寄存器(可以直接控制CPU与寄存器)
栈式:HotSpot虚拟机中的任何操作都需要入栈和出栈的步骤,即使用栈来管理运行
寄存器:典型应用是传统PC上x86的二进制指令集、Android的Davlik虚拟机(Google在Android上就选择了此种方案,这也是安卓程序无法跨平台原因)
由于跨平台性的设计,Java的指令都是根据栈式指令集架构来设计的。不同平台CPU架构不同,所以不能设计为寄存器架构的。栈式架构的优点是跨平台,指令集小,编译器容器实现;缺点是性能下降,实现相同的功能需要更多的指令且它的大小受限。
例:2+3
栈式:javap反编译:切换到idea的命令终端,打开class文件所在位置
javap -v TestJVM.class
2、JVM中的编译器中优化的JIT技术(热点代码)
简化图:
寻找热点代码提升并优化执行效率
HotSpot,Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler)
解释器的执行,抽象的看是这样的:输入的代码 -> [ 解释器 解释执行 ] -> 执行结果
而要JIT编译然后再执行的话,抽象的看则是:输入的代码 -> [ 编译器 编译 ] -> 编译后的代码 -> [ 执行 ] -> 执行结果
解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。
什么会被作为热点代码?
1、被多次调用的方法。
2、被多次执行的循环体。
热点代码探测:虚拟机会为每个方法(或是循环或其它代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阀值,就认为它是“热点方法”,直接触发JIT编译成为热点方法
- 方法计数器:
- 循环计数器
注意:其它的虚拟机都没有方法区的概念。
三大虚拟机:
相比Hotspot, JRockit不太关注用记响应时间和体验,更希望获得更高性能
J9在它自己的生态环境下性能很好,但换成其它可能就会遇到各种兼容问题
降低兼容性,依赖硬件研发的Taobao JVM,将生命周期长的对象移出堆内存中,避免被GC回收
3、虚拟机系统
简图:java类的7个生命周期图
详细执行过程:
初始化:静态变量初始化
4、类加载系统:加载、链接、初始化
加载具体过程:
1、通过一个类的全类名获取到定义此类的二进制字节流
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3、在内存中生成一个代表这个类的java.lang.Class的对象,作为方法区中这个类的各种数据访问的入口
加载.class的方式:
- 本地文件系统中直接加载
- 网络获取
- 从压缩包中读取:jar\war包
- 运行时动态加载:动态代理(反射)
- 由其它文件生成
- 从数据库中提取.class文件
- 从加密的文件中获取 ,如防Class文件被反编译的保护
链接阶段:
验证:
- 在于确保Class文件的字节流中包含信息符合当前虚拟机的要求,保证加载类的正确性,不会危害到虚拟机自身安全
- 四种验证:文件格式验证、元数据验证、字节码验证、符号引用验证
准备
- 为类变量分配内存并设置该类变量的默认初始值(即0)
- 这不含用final修饰的static就变成了常量,因为final在编译的时候就会分配了,准备阶段会显式初始化
- 这里不会为变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中
解析
- 将常量池内的符号引用转换为直接引用的过程
- 解析操作往往会随着JVM在执行完初始化之后再执行
- 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
- 解析主要是针对 类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等
初始化阶段:
- 初始化就是执行构造器方法<clinit>()的过程
- 此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的刘正铸合并而来
- 构造器方法中指令按语句在源文件中出现的顺序执行
- <clinit>()不见死不同于类的构造器。
- 若该类具有父类,JVM会保证子类的<clinit>执行前,父类的clinit已经执行完毕
- 虚拟机必须保证一个类的clinit方法在多线程下被同步加锁
public class StaticTest { private static int a1 = 10; static{ a1 = 20; c1 = 100; } private static int c1 = 0; public static void main(String[] args) { System.out.println(a1); System.out.println(c1); } }//output: /* StaticTest 20 0 */
idea中的jclasslib插件安装好了后,就可以编译完成后,点view-show Bytecode with Jclasslib
4.1 加载器分类:BootStrapLoader和AppClassLoader
BootStrapLoader:最外部的加载器,非java代码写的加载器,是由C或C++写的,如java的系统类(extClassLoader,String等)
AppClassLoader:用户自己写的类或一些类库
ExtClassLoader:AppClassLoader的父类加器载,也是像object\string\int等系统内置类直接的加载器
而ExtClassLoader的父类则为null
public class TestClassLoader { public static void main(String[] args) { ClassLoader cl1 = TestClassLoader.class.getClassLoader(); System.out.println("本类的加载器:" + cl1); System.out.println("本类的加载器的父类:" + cl1.getParent()); ClassLoader cl2 = ClassLoader.getSystemClassLoader(); System.out.println("系统类加载器父类:" + cl2.getParent()); System.out.println("系统类加载器父类的父类:" + cl2.getParent().getParent()); ClassLoader cl3 = Object.class.getClassLoader(); System.out.println("Object的加载器:" + cl3); ClassLoader cl4 = int.class.getClassLoader(); System.out.println("int的加载器:" + cl4); } }
本类的加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
本类的加载器的父类:sun.misc.Launcher$ExtClassLoader@497470ed
系统类加载器父类:sun.misc.Launcher$ExtClassLoader@497470ed
系统类加载器父类的父类:null
Object的加载器:null
int的加载器:null
public class ClassLoaderTest { public static void main(String[] args) { System.out.println("+++++++++启动类加载器+++++++++++"); //获取BootStrapClassLoader能够加载的api的路径 URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i]); } //从上面jar中找到一个随便类,查看其加载器 ClassLoader HKSCS = sun.awt.HKSCS.class.getClassLoader(); System.out.println("BootStrapClassLoader包中的类:" + HKSCS); //扩展类文件 System.out.println("++++++++++++扩展类加载器++++++++++++++"); String extDirs = System.getProperty("java.ext.dirs"); for (String s : extDirs.split(";")) { System.out.println(s); } ClassLoader aemel = com.sun.java.accessibility.util.AccessibilityEventMonitor.class.getClassLoader(); System.out.println(aemel); }
+++++++++启动类加载器+++++++++++
file:/D:/apps/Java/jdk1.8.0_251/jre/lib/resources.jar
file:/D:/apps/Java/jdk1.8.0_251/jre/lib/rt.jar
file:/D:/apps/Java/jdk1.8.0_251/jre/lib/sunrsasign.jar
file:/D:/apps/Java/jdk1.8.0_251/jre/lib/jsse.jar
file:/D:/apps/Java/jdk1.8.0_251/jre/lib/jce.jar
file:/D:/apps/Java/jdk1.8.0_251/jre/lib/charsets.jar
file:/D:/apps/Java/jdk1.8.0_251/jre/lib/jfr.jar
file:/D:/apps/Java/jdk1.8.0_251/jre/classes
BootStrapClassLoader包中的类:null
++++++++++++扩展类加载器++++++++++++++
D:\apps\Java\jdk1.8.0_251\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
sun.misc.Launcher$ExtClassLoader@e2144e4
Process finished with exit code 0
四种获取ClassLoader方法:
public static void main(String[] args) { System.out.println("第一种:clazz.getClassLoader"); ClassLoader c1 = GetClassLoader.class.getClassLoader(); System.out.println(c1); System.out.println("第二种:利用当前执行的线程"); ClassLoader c2 = Thread.currentThread().getContextClassLoader(); System.out.println(c2); System.out.println("第三种:获取系统的getSystemClassLoader"); ClassLoader c3 = ClassLoader.getSystemClassLoader(); System.out.println(c3); System.out.println("第四种:获取调用者的ClassLoader"); ClassLoader c4 = DriverManager.getDrivers().getClass().getClassLoader(); System.out.println(c4); }
第一种:clazz.getClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
第二种:利用当前执行的线程
sun.misc.Launcher$AppClassLoader@18b4aac2
第三种:获取系统的getSystemClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
第四种:获取调用者的ClassLoader
null
4.2 双亲委派机制
当某个类加载器需要加载某个.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
双亲委派机制作用:
1、防止重复加载同一个.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class
不能被篡改。通过委托方式,不会去篡改核心.class
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class
对象。这样保证了Class
执行安全。
我们有如下的加载器:当要加载类时,它会逐级往上交给最上层的加载器去检查是否已加载,避免因我们自己定义的类与系统的已加载过的类重名时发生自定义覆盖原始核心类的风险
- BootstrapClassLoader(启动类加载器)
c++
编写,加载java
核心库java.*
,构造ExtClassLoader
和AppClassLoader
。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
- ExtClassLoader (标准扩展类加载器)
java
编写,加载扩展库,如classpath
中的jre
,javax.*
或者java.ext.dir
指定位置中的类,开发者可以直接使用标准扩展类加载器。
- AppClassLoader(系统类加载器)
java
编写,加载程序所在的目录,如user.dir
所在的位置的class
- CustomClassLoader(用户自定义类加载器)
java
编写,用户自定义的类加载器,可加载指定路径的class
文件
经过一系列的递归加载后,凡是与父加载器重名的类都不会被加载覆盖进来,保证系统的安全性。