JVM 类加载 目录一、JDK组成二、javac1.编译目的2.编程语言分类3.编译过程4.Class文件三、类加载器1.类型2.双亲委派模式3.类加载过程4.自定义类加载器一、JDK组成Java开发工具包用于编译和运行Java程序包括内置类库.java用于编写java文件编译器javac 将java文件编译成class文件调试工具jmap、jstackJava运行环境JRE包括编译后的内置类库.class虚拟机JVM 用于执行class文件不同OS有不同的JVM实现二、javac1.编译目的编译的目标是将源代码转换为OS可执行的二进制指令常见的javac有IDEA、Eclipse。解释型语言在运行阶段编译会拖慢运行速度编译型语言在部署时编译部署时间长。为了解决上述两种语言的问题Java将编译过程分为预编译、后编译两部分javac负责预编译生成不同JVM通用的class文件。JVM负责后编译针对不同OS开发了特定的JVM用于生成OS可执行的二进制指令。2.编程语言分类编译型语言编译器在客户机的**部署时编译(程序应装入内存才能链接)**将源代码一次性翻译成OS可执行的机器码例如C、Go运行阶段速度快但部署时间长。解释型语言运行时编译解释器在运行阶段将源代码翻译为机器码将编译时间分摊到运行阶段没有部署时间但是运行时间较长。Java、Python语言属于编译解释型语言为了消除部署时间并减少运行时间将校验等操作解耦到部署前编译编译后的文件可以在客户机上直接部署也没有部署时间但是编译阶段不在客户机上所以类并没有具体内存地址链接阶段还是需要在运行时编译。3.编译过程词法分析将源代码拆分为token检查单个token是否符合约束。语法分析根据执行顺序基于token构造语法树检查语法是否正确。填充符号表类之间是相互引用的由于预编译阶段类、接口并未装入内存就需要以符号的形式记录类,符号(表示内存地址)到符号表同时检查是否有循环依赖。注解处理处理Override等编译时注解部分注解会修改语法树。语义分析遍历语法树进行类型检查、变量作用域检查。字节码生成遍历语法树将语法节点映射为JVM字节码指令例如变量赋值ldc、if判断if_icmpge同属性、方法、常量、引用记录到class文件。4.Class文件ClassFile{u4 magic;//Class 文件的标志u2 minor_version;//Class 的小版本号u2 major_version;//Class 的大版本号u2 constant_pool_count;//常量池的数量cp_info constant_pool[constant_pool_count-1];//常量池u2 access_flags;//Class 的访问标记u2 this_class;//当前类u2 super_class;//父类u2 interfaces_count;//接口数量u2 interfaces[interfaces_count];//一个类可以实现多个接口u2 fields_count;//字段数量field_info fields[fields_count];//一个类可以有多个字段u2 methods_count;//方法数量method_info methods[methods_count];//一个类可以有多个方法u2 attributes_count;//此类的属性表中的属性数attribute_info attributes[attributes_count];//属性表集合}class文件主要属性有vesion版本号用于记录程序编写时基于的JDK版本高版本JVM可以后编译低版本class文件但是低版本JVM无法编译高版本class文件。constant_pool常量池记录预编译阶段符号填充表的二进制等到真正确定内存地址JVM会检查符号表将符号转换为类、对象真正的内存地址。access_flags访问标记记录类的元信息包括类型、作用域等每个组合都对应一个二进制。fields属性表记录类的成员变量元信息包括作用域、返回值类性、可变性、可见性等每个组合都对应一个二进制。methods方法表记录类的方法元信息包括作用域、返回值类型、参数等内部还有一个attributes数组记录方法体中的二进制指令序列。三、类加载器1.类型类加载器负责将磁盘中的class文件加载到内存中。JVM 启动时并不会一次性加载所有的类而是根据需要去动态加载。BootstrapClassLoader是最顶层的类加载器用来加载JDK的类库。AppClassLoader负责加载应用中的引入的第三方类库和自定义类。2.双亲委派模式每个类加载器都维护一个HashMap记录全限定类名Class对象加载前先检查Map所以同一个ClassLoader相同全限定类名只会被加载一次但是不同ClassLoader可能会重复加载同一个类这就有类的重复加载问题。双亲委派模型会先将类加载请求转发给父类加载器在父类加载器没有找到所请求的类的情况下该类加载器才会尝试去加载确保了不会有重复加载问题。此外双亲委派模型还能防止核心API被篡改当项目中有多个String.class类时委派BootstrapClassLoader加载JDK中的String类防止AppClassLoader加载自定义的String类。3.类加载过程当访问的类不在内存中即内存中没有该类的Class对象就会触发类加载装入类加载器装入class文件到内存方法区并在堆中实例化Class对象作为访问方法区的入口。链接验证class版本号是否低于JVM版本号为static变量分配内存空间并赋初始值。final类型直接赋给定值。Java采用运行时动态链接将类中的符号引用替换为方法区中方法和类或接口的直接引用。这样当处理器执行一条指令时就能知道应该读取哪个内存空间中的对象数据并操作。初始化同一个类的初始化仅执行一次触发时机为类被new、调用静态变量静态方法、子类被加载、反射操作类时。初始化会执行类的静态代码块并为static变量赋指定值。特别的main方法对应的类在JVM进程启动时就会初始化。4.自定义类加载器一个Tomcat服务器对应一个JVM进程当Tomcat服务器中部署订单系统、库存系统等多个系统且不同系统中存在全类名相同的文件时使用一个类加载器就无法加载这些全类名相同的问题就要用到自定义类加载器打破双亲委派机制。每个系统使用自己的类加载器加载文件类加载时不进行双亲委派而是直接自己加载从而达到应用之间的类隔离。继承ClassLoader抽象类重写以下方法findClass(String name)给定要加载的类的二进制名称重写该方法后默认还是双亲委派机制先调用父加载器loadClass()方法加载如果加载失败才调用该方法内的逻辑加载。loadClass(String name, boolean resolve)重写该方法后类加载器会首先尝试自己加载name类。