大家好,我是阿叔,专注分享职场、产品、研发、管理等相关经验。
今天的主题是:Java代码编译和执行的过程!我们用思维导图的方式来进行沟通。如果需要本文的高清图片、思维导图源文件、excel文件的兄弟,请给“阿叔说研发”发私信:资料获取。
Java源代码编译
指令将Java源代码解析成JVM能够理解的字节码
由以下三个过程组成
分析和输入到符号表
注解处理
语义分析和生成 class 文件
生成的.clsss文件包含以下内容
结构信息。包括 class 文件格式版本号及各部分的数量与大小的信息。
元数据。对应于 Java 源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池。
方法信息。对应 Java 源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息。
Java类加载&执行class字节码
JVM 的类加载通过 ClassLoader 及其子类来完成
类加载器种类
启动类加载器
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的 class
拓展类加载器
负责加载Java平台中扩展功能的一些 jar 包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的 jar 包
应用程序类加载器
负责将应用的类路径(classpath)中中指定的 jar 包及目录中 class,可以通过ClassLoader.getSystemClassLoader()获取应用程序类加载器
自定义类加载器
开发者通过继承通过java.lang.ClassLoader类的方式实现自己的类加载器
双亲委派模型
类加载器在接收到类加载请求时,不会自己处理,而是先将类加载请求交给他的父级,依次到启动类加载器,如果启动类加载器能够找到则直接返回,否则再依次往下查找,其实无论哪种类加载器执行最后都会在启动类加载器中查找一遍
加载过程中会先检查类是否被已加载,检查顺序是自底向上,从 Custom ClassLoader 到 BootStrap ClassLoader 逐层检查,只要某个 Classloader 已加载就视为已加载此类,保证此类只所有 ClassLoade r加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
Java类加载机制
加载
JVM类加载的第一阶段,把代码加载到内存中,JVM将磁盘,网络中的字节码转换为二进制流加载到内存中,并在JVM的方法区中为这个类创建对应的Class对象
验证
JVM规范校验
JVM 会对字节流进行文件格式校验,判断其是否符合 JVM 规范,是否能被当前版本的虚拟机处理
代码逻辑校验
JVM 会对代码组成的数据流和控制流进行校验,确保 JVM 运行该字节码文件后不会出现致命错误。例如一个方法要求传入 int 类型的参数,但是使用它的时候却传入了一个 String 类型的参数
准备
内存分配的对象
Java 中的变量有「类变量」和「类成员变量」两种类型,「类变量」指的是被 static 修饰的变量,而其他所有类型的变量都属于「类成员变量」。在准备阶段,JVM 只会为「类变量」分配内存,而不会为「类成员变量」分配内存。「类成员变量」的内存分配需要等到初始化阶段才开始。
初始化的类型
final修饰的常量则直接赋予用户所希望的值
初始化为该类型的零值(0,null)
解析
当通过准备阶段之后,JVM 针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类引用进行解析。这个阶段的主要任务是将其在常量池中的符号引用替换成直接其在内存中的直接引用。
初始化
使用
JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。
卸载
当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存
Java类执行
确定类变量的初始值
在类加载的准备阶段,JVM 会为类变量初始化零值,这时候类变量会有一个初始的零值。如果是被 final 修饰的类变量,则直接会被初始成用户想要的值
初始化入口方法
当进入类加载的初始化阶段后,JVM 会寻找整个 main 方法入口,从而初始化 main 方法所在的整个类。当需要对一个类进行初始化时,会首先初始化类构造器(),之后初始化对象构造器()
初始化类构造器
JVM 会按顺序收集类变量的赋值语句、静态代码块,最终组成类构造器由 JVM 执行。
初始化对象构造器
JVM 会按照收集成员变量的赋值语句、普通代码块,最后收集构造方法,将它们组成对象构造器,最终由 JVM 执行
如果在初始化 main 方法所在类的时候遇到了其他类的初始化,那么就先加载对应的类,加载完成之后返回。如此反复循环,最终返回 main 方法所在类。
完整的思维导图