ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

一个.class文件是怎么被JVM跑起来的?类加载机制+双亲委派一篇讲透

一个.class文件是怎么被JVM跑起来的?类加载机制+双亲委派一篇讲透 一个 .class 文件是怎么被 JVM 跑起来的类加载机制 双亲委派一篇讲透你写的 Java 代码编译成.class之后JVM 到底对它做了什么才能让它真正跑起来为什么静态代码块只执行一次为什么你自己写一个java.lang.String不会生效JDBC 加载驱动为什么非得用Class.forName这些问题的答案全都指向同一个东西——类加载机制。今天咱们就把它彻底讲明白。一、类的一生7 个阶段一个类从进 JVM 到被卸载要走 7 个阶段加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载其中验证、准备、解析合起来叫连接。打个比方加载就像把原材料运进工厂验证是质检准备是先把货架摆好放上空盒子解析是把说明书上的零件名翻译成仓库里的具体位置初始化才是真正开工组装。有个细节解析阶段的顺序不固定它可以拖到初始化之后这是为了支持多态运行时才知道具体调哪个方法。二、加载把字节流搬进内存加载阶段类加载器干 3 件事通过类的全限定名拿到这个类的二进制字节流不一定是 .class 文件也能来自 jar、网络、甚至动态生成把字节流的静态结构转换成方法区里的运行时数据结构在堆里生成一个代表这个类的java.lang.Class对象以后反射、new 都靠它当入口三、连接三阶段验证先过安检验证就是防止一份恶意的、损坏的字节流搞崩虚拟机。要查四样文件格式开头魔数是不是0xCAFEBABE、元数据有没有乱继承 final 类、字节码方法里的逻辑合不合法、符号引用引用的东西找不找得到。准备静态变量先给零值高频考点准备阶段给静态变量static分配内存先设成零值这时候一行你的代码都还没执行。publicstaticintvalue123;准备阶段结束后value是0不是 123。赋值成 123 这个动作得等到初始化阶段。各种类型的零值int 是 0、boolean 是 false、引用类型是 null、char 是\u0000。但有个特例——static final编译期常量准备阶段就直接赋好值publicstaticfinalintCONSTANT123;这种情况下准备阶段CONSTANT就已经是 123 了。解析符号引用变直接引用解析就是把常量池里的符号引用换成直接引用。符号引用只是个名字比如类的全限定名跟内存无关直接引用是真正的内存指针、偏移量能直接定位到目标。四、初始化真正执行你的代码初始化阶段 JVM 会执行一个叫clinit()的方法它是编译器把你写的所有静态变量赋值 静态代码块自动合并出来的。两条关键规则执行子类的clinit前保证父类的先执行JVM 会给clinit加锁保证多线程下一个类只初始化一次这就是静态内部类单例线程安全的原理初始化顺序实测classParent{staticinta1;static{System.out.println(父类静态代码块 aa);}{System.out.println(父类构造代码块);}Parent(){System.out.println(父类构造方法);}}classChildextendsParent{staticintb2;static{System.out.println(子类静态代码块 bb);}{System.out.println(子类构造代码块);}Child(){System.out.println(子类构造方法);}}第一次new Child()的真实输出父类静态代码块 a1 子类静态代码块 b2 父类构造代码块 父类构造方法 子类构造代码块 子类构造方法第二次再new Child()静态代码块不执行了类只初始化一次只剩构造代码块和构造方法。什么时候会触发初始化记住 6 种主动引用new 对象、读写非 final 静态字段、调用静态方法、反射Class.forName、初始化子类时父类先初始化、启动时含 main 方法的主类。五、类加载器三兄弟加载器负责加载特点Bootstrap 启动类加载器lib 核心库java.lang.*C 写的Java 里拿到的是 nullPlatform 平台类加载器lib/ext 扩展库JDK9 前叫 ExtensionApplication 应用类加载器classpath 你的类默认加载器为什么String.class.getClassLoader()返回 null因为 String 是 Bootstrap 加载的Bootstrap 是 C 实现、没有对应的 Java 对象所以用 null 表示。六、双亲委派模型核心中的核心它是怎么工作的一个类加载器收到加载请求自己不先动手而是一路向上委托给父加载器直到 Bootstrap。父加载器说我这边找不到子加载器才会自己去加载。为什么非得这么绕两个好处避免重复加载、保证类唯一同一个类不管被谁请求最终都由同一个加载器加载不会出现两个一样的类安全你要是自己写了个java.lang.String想搞事情请求会向上委托给 Bootstrap加载的还是 JDK 自带的那个真 String你的假的根本不会被加载七、谁打破了双亲委派场景怎么打破的JDBC 等 SPIjava.sql.Driver接口在 Bootstrap但厂商驱动实现在 classpathBootstrap 看不见于是用线程上下文类加载器反向加载Tomcat每个 webapp 一个独立类加载器先自己加载再委托实现应用之间类隔离OSGi / 热部署网状加载器模块能动态装卸八、两个高频追问Class.forName 和 ClassLoader.loadClass 有啥区别Class.forName默认会初始化类执行静态代码块所以加载 JDBC 驱动时能顺便完成驱动注册ClassLoader.loadClass只加载、不初始化把初始化推迟到真正使用。静态内部类单例为什么线程安全外部类加载时不会马上加载内部类第一次调用才加载而初始化阶段 JVM 自动加锁保证实例只建一次所以既线程安全又懒加载还没有 synchronized 的开销。九、口诀总结口诀意思加载验证准备解析初始化类生命周期解析可延后准备零值初始化真值static 变量准备阶段是 0static final 准备就赋值编译期常量特例静态先行父先子父类静态块先执行只一次收到请求先向上委托双亲委派双亲委派两好处唯一安全不重复、防伪造核心类SPI 用线程上下文加载器打破双亲委派典型到这里JVM 类加载机制就讲完了。加上前面的内存结构和垃圾回收JVM 三大核心你已经拿下两块半。下一篇咱们聊JVM 调优实战——OOM、CPU 飙高、频繁 Full GC 到底怎么排查。觉得有用点个赞关注一下跟着系列走面试不慌。#Java #JVM #类加载 #双亲委派 #java面试 #后端开发
返回列表