• 极客时间 深入拆解java虚拟机 一至三讲学习总结


    为什么要学习java虚拟机

    1、学习java虚拟机的本质,是了解java程序是如何被执行且优化的。这样一来,才可以从内部入手,达到高效编程的目的。与此同时,你也可以为学习更深层级、更为核心的java技术打好基础。
    2、学习java虚拟机的好处
    (一)可以针对自己的应用,最优化匹配运行参数。
    (二)可以更好地规避虚拟机在使用中的bug,也可以更快地识别出java虚拟机中的错误。
    (三)学习最前沿、最成熟的垃圾回收算法实现以及及时编译器的实现,对以后学习其他的代码托管技术很有帮助。
    (四)虚拟机也可以运行其他语言,了解这些语言的通用体制,甚至让这些语言共享生态系统。
    3、课程内容
    (一)基本原理:剖析java虚拟机的运行机制,逐一介绍java虚拟机的设计决策以及工程实现。
    (二)高效实现:探索java编译器,以及内嵌于java虚拟机中的及时编译器,更好理解java语言特性,继而写出简洁高效的代码。
    (三)代码优化:介绍如何利用工具定位并解决代码中的问题,以及在已有工具不适用的情况下,如何打造专属轮子。
    (四)虚拟机黑科技

    第一节 Java代码是如何运行的?

    为什么java要在虚拟机里运行?

    语法复杂,抽象度高,在硬件上实现不现实。
    转换思路:设计一个面向java语言特性的虚拟机,并通过编译器将java程序转换成该虚拟机所能识别的指令序列,也称java字节码(操作码都被固定为一个字节)。然后java虚拟机可以由硬件实现(一次编写,到处运行)
    虚拟机附带托管环境(自动内存管理、垃圾回收、还提供诸如数组越界、动态类型、安全权限等动态检测)

    java虚拟机具体是怎样运行java字节码的?

    1、首先将编译成的class文件加载到java虚拟机中
    2、在运行过程中,每当调用进入一个java方法,java虚拟机会在当前线程的java方法栈中生成一个栈帧,用以存放局部变量以及字节码的操作数。、
    3、当退出当前的执行方法时( 不管正常返回还是异常返回),java虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。
    4、将字节码翻译成机器码。两种形式:(一)解释执行:逐条将字节码翻译成机器码并执行。(二)即时编译(JIT)将一个方法中包含的所有字节码编译成机器码后再执行。前者的优势在于无需等待编译,后者的优势在于实际运行速度更快。
    对不常用的代码解释执行,对小部分热点代码及时编译。

    第二节 java的基本类型

    java虚拟机中的boolean类型

    在java虚拟机规范中,boolean类型被映射成int类型。“true”被映射为整数1,“false”被映射为整数0。

    public class Foo {
      public static void main(String[] args) {
        boolean 吃过饭没 = 2; // 直接编译的话 javac 会报错
        if (吃过饭没) System.out.println(" 吃了 ");
        if (true == 吃过饭没) System.out.println(" 真吃了 ");
      }
    }
    
    # Foo.main 编译后的字节码
     0: iconst_2       // 我们用 AsmTools 更改了这一指令
     1: istore_1
     2: iload_1
     3: ifeq 14        // 第一个 if 语句,即操作数栈上数值为 0 时跳转
     6: getstatic java.lang.System.out
     9: ldc " 吃了 "
    11: invokevirtual java.io.PrintStream.println
    14: iload_1
    15: iconst_1
    16: if_icmpne 27   // 第二个 if 语句,即操作数栈上两个数值不相同时跳转
    19: getstatic java.lang.System.out
    22: ldc " 真吃了 "
    24: invokevirtual java.io.PrintStream.println
    27: return
    

    上述程序运行只会打印“吃了”而不会打印“真吃了”,是因为对于java虚拟机来说,它看到的boolean类型,早已被映射为整数类型。因此,将原本声明为boolean类型的局部变量,赋值为出了0,1以外的整数值,在java虚拟机看来是“合法”的。

    java基本类型

    正无穷和负无穷分别是是0x7F800000和0xFF800000
    0xF800001等对应NaN(Not-a-Number)
    0x7FC00000为标准NaN,其他为不标准NaN。
    NaN特性:除了“!=”始终返回true之外,所有其他比较结果都会返回false。

    java类型的基本大小

    栈帧有两个主要的组成部分
    (一)局部变量区
    在虚拟机规范中,局部变量区等价于一个数组,并且可以用正整数来所引。long、double值需要用两个数组单元来存储,boolean、byte、char、short和int在栈上占用的空间是一样的,引用类型也是一样的。(仅限于局部变量)
    值读入字段或数组时相当于做了一次隐式掩码操作。(截取高位字节保证读入数据满足相应的字节要求)
    boolean类型只读取最后一位读入字段或数组。
    (二)字节码的操作数栈
    java虚拟机的算术运算几乎全部依赖于操作栈。我们需要将堆中的boolean、byte、char以及short加载到操作栈上,而后将栈上的值当成int类型来运算。
    对于boolean、char两个类型来说,加载伴随零扩展。加载时会被复制到int类型的低二字节,而高二字节会用0来填充。
    对于byte、short这两个类型来说,加载伴随符号扩展。加载时会被复制到int类型的低二字节。然后根据正负高二字节用0或1填充。

    java虚拟机是如何加载java类的

    从class文件到内存中的类,按先后顺序需要经过加载、链接以及初始化三大步骤。

    public class Foo {
      static boolean boolValue;
      public static void main(String[] args) {
        boolValue = true; // 将这个 true 替换为 2 或者 3,再看看打印结果
        if (boolValue) System.out.println("Hello, Java!");
        if (boolValue == true) System.out.println("Hello, JVM!");
      }
    }
    

    对于上述程序,当替换为2时候无输出,当替换为3时候打印HelloJava及HelloJVM。因为boolean的掩码处理是取低位最后一位,2取低位最后一位为0,3取低位最后一位为1。

    java虚拟机是如何加载java类的?

    加载

    是指查找字节流,并且据此创建类的过程。对于数组类来说,是由java虚拟机直接生成。对于其他类来说,java虚拟机需要借助类加载器来完成查找字节流的过程。
    启动类加载器没有java对象,在java中只能用null来指代。除了启动类加载器,其他的类加载器都是java.lang.ClassLoder的子类,因此有对应的java对象。这些类加载器需要先由另一个类加载器,比如说启动类加载器,加载至java虚拟机中,放能执行类加载。
    双亲委派模型:每当一个类加载器接收到加载请求时,它会先将请求转发给父类加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。
    另外两个重要的类加载器是扩展类加载器和应用类加载器,均由java核心类库提供。
    三个重要类加载器功能
    1、启动类加载器负责加载最为基础最为重要的类。
    2、扩展类加载器的父类是启动类加载器,扩展类加载器负责加载相对次要、但又通用的类。
    3、应用类加载器的父类是扩展类加载器,它负责加载应用程序路径下的类。
    在java虚拟机中,类的唯一性是由类加载器实例以及类的全名一同决定的。即便是同一串字节流,经由不同的类加载器加载,也会得到两个不同的类。

    链接

    是指将创建成的类合并至java虚拟机中,使之能够执行的过程。它可以分为验证、准备以及解析三个阶段。
    (一)验证阶段
    的目的,在于确保被加载类能够满足java虚拟机的约束条件。
    (二)准备阶段
    的目的是为被加载类的静态字段分配内存。部分java虚拟机还会构造其他跟类层次相关的数据结构,比如说用来实现虚方法的动态绑定方法表。在class文件被加载至java虚拟机之前,这个类无法知道其他类及其方法、字段所对应的具体地址,甚至不知道自己的方法、字段的地址。因此,每当需要引用这些成员时,java编译器会生成一个符号引用。
    (三)解析阶段的目的,正是将这些符号引用解析成为实际引用。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载。

    初始化

    如果要初始化一个静态字段,可以在声明时直接赋值,也可以在静态代码块中对其赋值。如果直接赋值的静态字段被final修饰,并且它的类型是基本类型或字符串时,那么该字段便会被java编译器标记成常量值,起初始化直接由java虚拟机完成。除此之外的直接赋值操作,以及所有静态代码块中的代码,则会被java编译器置于同一方法中,并把它命名为。类加载的最后一步是初始化,便是为标记为常量值的字段赋值,以及执行方法的过程。java虚拟机会通过加锁来确保,类的方法仅被执行一次。
    类的初始化触发情况:
    1、当虚拟机启动时,初始化用户指定的类。
    2、当遇到用以新建目标类实例的new命令时,初始化new命令的目标类。
    3、当遇到调用静态方法的指令时,初始化该静态方法所在的类。
    4、党遇到访问静态字段的指令时,初始化·该静态字段所在的类。
    5、子类的初始化会触发父类的初始化。
    6、如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化。
    7、使用反射API对某个类进行反射调用时,初始化这个类。
    8、当初次调用MethodHandle实例时,初始化该MethodHandle指向的方法所在的类。

    public class Singleton {
      private Singleton() {}
      private static class LazyHolder {
        static final Singleton INSTANCE = new Singleton();
        static {
          System.out.println("LazyHolder.<clinit>");
        }
      }
      public static Object getInstance(boolean flag) {
        if (flag) return new LazyHolder[2];
        return LazyHolder.INSTANCE;
      }
      public static void main(String[] args) {
        getInstance(true);
        System.out.println("----");
        getInstance(false);
      }
    }
    

    1、新建数组会导致LazyHolder的加载吗?会导致它的初始化吗?
    答:会加载元素类LazyHolder,不会初始化元素类。
    2、新建数组会导致LazyHolder的来链接吗?
    答:不会链接元素类LazyHolder;在getinstance(false)时才真正链接和初始化。

  • 相关阅读:
    Mac电脑,Andorid studio 配置 Flutter
    java.lang.RuntimeException: com.intellij.ide.plugins.PluginManager
    #Java Web累积#关于MUI的上滑和下拉加载
    #iOS问题记录#WKWebView 闪退异常
    #iOS问题记录#UITableView加载后直接滑动倒最底部
    #Java Web累积#JS动态加载所有同name的select的option
    #iOS问题记录# UIWebView滑动到底部
    #Java Web累积#表格<table>中隐藏列做备用数据
    #iOS问题记录# UITextview富文本链接,禁止长按事件
    Day 1:自定义tableview cell xib版
  • 原文地址:https://www.cnblogs.com/luoleqi/p/10677268.html
Copyright © 2020-2023  润新知