写点什么

jvm(三) 类加载机制、javac 编译

作者:想要飞的猪
  • 2022-11-21
    广东
  • 本文字数:3570 字

    阅读完需:约 12 分钟

  1. 概述 Java 虚拟机把描述类信息的数据从 Class 文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被 jvm 直接使用的 java 类型,这个过程称为虚拟机的类加载机制。

  2. 类加载的时机一个类从被加载到虚拟机到卸载,它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三部分称为链接。加载 ->验证->准备->初始化->卸载这五个阶段的顺序是不能变的,类型的加载必须按照这种顺序开始,而解析阶段不一定:它可以在某些情况下线进行初始化然后再进行解析,这是为了支持 java 的动态绑定特性。类的加载时机 《java 虚拟机规范》中并没有强制约束,有虚拟机具体实现,但是对初始化做出了严格规定有且只有六种情况必须对类进行初始化:①、遇到 new 、getstatic、putstatic 或者 invokestatic 这四条指令时,如果类没有进行过初始化,则需要先出发其初始化阶段。以下几种场景会生成这些指令;

  3. 使用 new 关键字实例化对象时候。

  4. 读取一个累的静态字段的时候(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)。

  5. 调用一个类的静态方法的时候。②、使用 java.lang.reflect 包的方法对类型进行反射调用的时候,如果类还没有初始化过,则出发初始化动作。③、初始化子类时,父类没有初始化时,先初始化父类④、当虚拟机启动时,执行包含 main 函数的类,会先初始化⑤、当一个接口中定义了 default (jdk8 之后)方法时,实现了此接口类,则该接口要在该类初始化之前初始化。⑥、当使用 jdk 7 新加入的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial 四种类型的方法句柄,并且这句柄对应的类么有初始化过,则需要先初始化。除了以上六种,其余的都是被动引用,不会出发初始化。如:public class A{static{System.out.println("Supper Class A init!");}public static int value = 1;}public class B extents A{static{System.out.println("Sub Class B init!");}}/**

  6. 非主动使用类字段,输出结果:Supper Class A init!*/pulic class NotInitSupperClass{public static void main(String[] args){System.out.println(B.value);}}

  7. 类加载过程 3.1、加载 1)、通过一个类全限定名来获取定义此类的二进制字节流(并没有限定从哪里获取,可以在网络,zip 压缩包、运行时生成,其他文件生成、数据库中读取,等等)。2)、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。3)、在内存总生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

  8. 3.2、验证

  9. 验证是链接的第一步,主要目的是确保 Class 文件的字节流中包含的信息都符合《java 虚拟机规范》的约束要求,保证不会有损坏虚拟机的代码;


  • 是否以魔数 oxCAFEBABE 开头

  • 主次版本号是否被当前虚拟机支持

  • 常量池中的是否有不被支持的常量等等

  • 3.3、准备

  • 准备阶段是正式位类中静态变量分配内存,并设置变量初始值的阶段;

  • 3.4)、解析

  • 3.5)、初始化


4、类加载器


     类加载器是通过一个类的全限定名称来获得该类的二进制字节流的实现代码。
4.1、类加载器虽然只作用在加载动作,但是作用却不止加载阶段,对于任意一个类,都必须由这个类本身以及加载这个类的加载器共同确定java 类在虚拟机中的唯一性。
4.2、双亲委派模型
在虚拟机角度,加载器分为两种,一启动类加载器(Bootstrap ClassLoader),这个类加载器是c++实现,是虚拟机的一部分;另外一种就是其他类型的加载器,是由java语义实现,在虚拟机外部。
在开发人员角度来看分为四种:
复制代码


  • 启动类加载器(BootStrap ClassLoader):主要加载放在 JAVA_HOME/lib 下面的类,或者被 -Xbootclasspath 参数制定的路径中存放的,并且能被 jvm 识别的类库;

  • 扩展类加载器(Extension Class loader):主要加载 java_home/lib/ext 目录中的类,或者是被 java.ext.dirs 系统变量所制定的路径中的类库;

  • 应用程序类加载器(Application ClassLoader):这个类加载器主要加载用户类路径上所有的类库;

  • 用户自定义类加载器:实现 ClassLoader 接口重写 findClass()方法;以上四种加载器都有层级关系 bootStrap ClassLoader --->extension ClassLoader --->Application ClassLoader ------> 用户自定义类加载器;


双亲委培机制:类加载器在收到加载一个类的请求时,它首先不会自己马上去加载,而是把这个请求委派给父类加载器去完成,每一层加载器都是如此,因此所有的类加载请求都会传送到最顶层 bootStrap ClassLoader,只有当父类加载器无法完成时,自己才回去加载;


使用双亲委培模型,1、可以保证基类的安全性,保证核心类不被篡改;2、防止重复加载同一个.class;
4.3、破话双亲委派机制
1)、classLoader 中的dindClass()方法
2)、线程上下文类加载器:通过java.lang.Thread类的setContext-ClassLoader()方 法进行设置;通过线程上下文加载器,可以实现父类加载器。去请求子类加载器完成类加载行为;
3)、动态扩展的追求:代码热替换(Hot Swap)、模块热部署(Hot Deployment)
tomcat没有严格的遵守双亲委派机制,因为双亲委派机制无法满足tomcat 的要求;如:一个tomcat、webapps 下部署了两个应用
app/lib/a-1.0.jar/xxx.Abc
app/lib/a-2.0.jar/xxx.Abc
复制代码


不同版本中 Abc 类的内容是不一样的,代码不同;


tomcat 自定义了 类加载器:


  • commons 类加载器:主要加载 tomcat 已经应用通用的一些类,位于 catalina_home/lib 下

  • catalina ClassLoader 用于加载服务器内部可见类,这些类应用程序不能访问

  • shared ClassLoader 加载应用程序共享类

  • web app ClassLoader 每个程序都会有一个独一无二的 webapp ClassLoader,它用来加载 WEB_INF/classes 和 WEB-INF/lib 下的类;


5、程序编译于代码优化


javac 编译期是使用java语言编写的一个程序,在jdk6之前独立于javaSE API 的一部分,之后晋升为标准的java类库之一。
复制代码


5.1、javac 总体过程大致分为 1 个准备和 3 个处理过程,分别如下:


1)、准备过程:初始化插入式注解器。


2)、解析于填充符号过程,包括:词法、语法分析。将源码中的字符流转换为标记集合,构造出抽象语法树。填充符号。产生符号地址和符号信息。


3)、插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段。


4)、分析于字节码烧给你吃过程,包括:


  • 标注检查,对语法的静态信息进行检查。

  • 数据流以及控制流分析,对程序动态运行过程进行检查。

  • 解语法糖。将简化代码编写的额语法糖还原为原有的形式。

  • 字节码生成。


5.2、语法糖


在几计算机语言中添加一些语法,这种语法对最终的编译结果和功能没有实际影响,但是可以提高效率、或者提升语法的严谨性,或能减少编码出错的机会,
复制代码


java 中一些语法糖:


泛型、自动装箱、自动拆箱、遍历循环、变长参数、条件编译 、内部类、枚举类、断言语句、数值字面量、


范型:java 中的范型为“类型擦除式泛型” 也就是在编译后全部替换为原来的裸类型,与之相反的是 C#的“具现化式泛型”


自动装箱,拆箱于循环遍历:根据编译后的代码就可以知道使用遍历循环时为啥要实现 Iterable 接口的原因了。


public static void main(String[] args) {     List<Integer> list = Arrays.asList(1, 2, 3, 4);     int sum = 0; for (int i : list) {         sum += i;     }    System.out.println(sum); }//编译后:public static void main(String[] args) {     List list = Arrays.asList( new Integer[] {                     Integer.valueOf(1),                                     Integer.valueOf(2),                                           Integer.valueOf(3),        Integer.valueOf(4) });         int sum = 0;    for (Iterator localIterator = list.iterator(); localIterator.hasNext(); )     { int i = ((Integer)localIterator.next()).intValue();           sum += i;     }System.out.println(sum); }



自动拆箱装箱的陷阱:包装类的 “== ”运算在不遇到算术运算的情况下不会自动拆箱,以及他们的equals()方法不处理数据类型的转型关系,e == f 为 false的原因是Integer的 cache中只有-128 -127 的缓冲(享元模式)
public static void main(String[] args) {
Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; System.out.println(c == d);//true System.out.println(e == f);//false System.out.println(c == (a + b));//true System.out.println(c.equals(a + b));//true System.out.println(g == (a + b));//true System.out.println(g.equals(a + b));//false

}
复制代码


用户头像

还未添加个人签名 2020-06-05 加入

还未添加个人简介

评论

发布
暂无评论
jvm(三)类加载机制、javac编译_JVM类加载_想要飞的猪_InfoQ写作社区