理解Java虚拟机(2)之.class文件加载过程

本文是《深入理解Java虚拟机》读书笔记,详细介绍了.class文件加载过程,包括加载、验证、准备、解析和初始化阶段。加载由ClassLoader完成,遵循双亲委派模型。验证确保.class文件符合JVM规范,准备阶段为类变量分配内存并设置初始值,解析阶段将符号引用替换为直接引用。初始化阶段执行类定义的代码,开始程序的实际运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

理解Java虚拟机(2)之.class文件加载过程

读《深入理解Java虚拟机》-周志明 读书笔记

虚拟机只能执行.class文件,在.class文件加载过程中,生命周期包括:加载,验证,准备,解析,初始化使用卸载

image

  • 加载将.clss文件加载进虚拟机,加载来源有

    • 1.常见jar包;
    • 2.网络获取(dubbo的RPC是典型,底层通过java的RMI方法,通过固定协 议,将远端生成者接口,对象序列化,消费者调用);
    • 3.运行时动态生成,典型代表反射技术;
    • 4.从其他文件,如jsp;
    • 5.通过数据库读取,(没见到过,书里说中间件服务器)

    加载完成后,虚拟机就会按照.class文件里面的内容相应的存储到内存的方法区(方法区是虚拟机加载类信息,常量,静态变量,即使编译后的代码,是线程共享的,简单讲就是加载的东西放在这里面),加载是通过ClassLoader这个加载,这个加载机制是通过(双亲委派模型,后面详细讲双亲委派模型,简单讲就是JVM有一个系列的继承加载,自己也可以自定义ClassLoader,先通过自己的ClassLoader,在通过JVM的ClassLoader),平常使用Tomcat,Jetty,这些容器都是有自己的ClassLoader,用过maven配置jetty启动时,都会在pom.xml配置标签,这个就是去配置jetty的ClassLoader。

  • 验证
    验证是连接阶段的第一步,并不是加载完了才验证(这样的太傻瓜式了,要是很多.class文件,在最开始加载就有错误,等加载完了再验证那就不用工作了),验证和加载是相辅相成的。在.java编译成.class文件里讲过.class打头就是个”cafebabe”的魔数,首先验证这个,不是这个打头的必然报错,接着看看版本号,版本号是不是java没有的(比方搞个java6.0,必然错了),然后看常量类型啊,验证相当于看这个.class文件是否符合JVM规范,用过Eclipse的就知道,每次”ctrl+s”就能看到自己写的代码有没有错误,因为eclipse就是有自己的CLassLoader,即时编译,你一保存就给你编译了.class,打开文件夹就能看到class目录的文件夹,然后通过去验证编译后的.class是不是符合JVM规范,然后错了就各种红叉叉,iead就没有即时编译,”ctrl+s”就找不到对应的class文件夹,eclipse的验证不是虚拟机的验证,这只是eclipse自己根据jVM规范验证的,编译后的.class文件JVM在加载的过程中,还是要自己验证的,不过虚拟机有个参数可以设置不验证(-Xverify:none),这样可以加快加载速度,当然前提是你相信.class符合规范,验证是很重要的一件事,不然怎么承受恶意的攻击,使得代码更安全,当然验证也会使得加载时间长,在安全和时间上,还是选择更安全些,毕竟java大都都是用来做企业级应用的,安全是最重要的。

  • 准备
    准备阶段是正式给类变量(static修饰的变量)分配内存并设置类变量的初始值,实例变量(不是static修饰的类层面的变量)会在对象实例化时随着对象一起分配在java堆中,要注意的是初始值的含义,和final修饰的实例变量,初始值指数据类型的0值,下面a在这个时候初始值为0;final修饰的变量这个时候会给值,c此时的值就是为3;(感觉final修饰符有些破坏了面向对象的设计,也不知道这个设计的初衷)

public Class Test(){
    private static int a = 1;//类变量
    private int b = 2 ;//实例变量
    private final int c = 3 ;
}
  • 解析
    解析是虚拟机将常量池(常量池位于方法区)的符号引用替换成直接引用的过程,先解释下符号引用直接引用
    符号引用:
   public class Test {
    public static  int sa = new Random().nextInt();
    }
    public class TestClass {
    //这里就是符号引用
    public static  int sa = Test.sa;
    public static  int sb = 1;

}

编译下上上面的,再反编译过来如下(javap -verbose TestClass.class):


D:\learn\myLearn\readBook\src\main\java\JVM>javap -verbose TestClass.class
Classfile /D:/learn/myLearn/readBook/src/main/java/JVM/TestClass.class
  Last modified 2016-8-12; size 330 bytes
  MD5 checksum 96ed1ae561b16a0cfecbdf7518c947cb
  Compiled from "TestClass.java"
public class JVM.TestClass
  SourceFile: "TestClass.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#17         //  java/lang/Object."<init>":()
   #2 = Fieldref           #18.#19        //  JVM/Test.sa:I
   #3 = Fieldref           #5.#19         //  JVM/TestClass.sa:I
   #4 = Fieldref           #5.#20         //  JVM/TestClass.sb:I
   #5 = Class              #21            //  JVM/TestClass
   #6 = Class              #22            //  java/lang/Object
   #7 = Utf8               sa
   #8 = Utf8               I
   #9 = Utf8               sb
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               <clinit>
  #15 = Utf8               SourceFile
  #16 = Utf8               TestClass.java
  #17 = NameAndType        #10:#11        //  "<init>":()V
  #18 = Class              #23            //  JVM/Test
  #19 = NameAndType        #7:#8          //  sa:I
  #20 = NameAndType        #9:#8          //  sb:I
  #21 = Utf8               JVM/TestClass
  #22 = Utf8               java/lang/Object
  #23 = Utf8               JVM/Test
{
  public static int sa;
    flags: ACC_PUBLIC, ACC_STATIC

  public static int sb;
    flags: ACC_PUBLIC, ACC_STATIC

  public JVM.TestClass();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."
":()V
         4: return
      LineNumberTable:
        line 8: 0

  static {};
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  // Field JVM/Test.sa:I
         3: putstatic     #3                  // Field sa:I
         6: bipush        114
         8: putstatic     #4                  // Field sb:I
        11: return
      LineNumberTable:
        line 9: 0
        line 11: 6
}

截部分看看

java/lang/Object."<init>":()
   #2 = Fieldref           #18.#19        //  JVM/Test.sa:I
   #3 = Fieldref           #5.#19         //  JVM/TestClass.sa:I

   static {};
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  // Field JVM/Test.sa:I
         3: putstatic     #3                  // Field sa:I 
         6: bipush        114
         8: putstatic     #4                  // Field sb:I  
最后satatic{}可以看到Test.java中sa(#3 = Fieldref          #5.#19 //  JVM/TestClass.sa:I)变量指向的TestClass(JVM/Test.sa:I 3: putstatic     #3   // Field sa:I ),初始化就是将这种指向变成指向new Random().nextInt(),的具体内存地址,这样才能真正的调用直接引用就是直接new 对象了,将句柄指向对象的指针地址;在解析的过程中,遇到引用其他的类,就去触发对应类的加载,最后都是直接引用
  • 初始化

初始化是类加载的最后一步,这个阶段才是真正执行类中定义的代码,在准备阶段,变量已经赋值过系统要求的值,初始化阶段根据我们写代码的主观习惯去初始化变量和其他资源。相当于程序真正运行起来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值