jvm01:指令架构、虚拟机:hotspot解释器与JIT热点代码协同执行、类加载过程、类加载器分类、双亲委托机制

本文介绍了Java虚拟机的两种指令架构:栈式和寄存器,重点讲解了HotSpot虚拟机中的JIT技术,如何通过识别热点代码提升执行效率。同时,阐述了JVM的类加载过程,包括加载、链接、初始化等步骤,以及类加载器的分类和双亲委派机制的作用,强调其在保证数据安全和执行安全方面的重要性。
摘要由CSDN通过智能技术生成

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.*,构造ExtClassLoaderAppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

  • ExtClassLoader (标准扩展类加载器)

java编写,加载扩展库,如classpath中的jrejavax.*或者
java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。

  • AppClassLoader(系统类加载器)

java编写,加载程序所在的目录,如user.dir所在的位置的class

  • CustomClassLoader(用户自定义类加载器)

java编写,用户自定义的类加载器,可加载指定路径的class文件

 经过一系列的递归加载后,凡是与父加载器重名的类都不会被加载覆盖进来,保证系统的安全性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pub.ryan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值