JVM入门(Class格式及类加载)

本文详细介绍了Java程序的执行流程,从源代码编译为字节码开始,到类的加载、连接、初始化过程,再到JVM如何解析Class文件格式。同时,文章还深入探讨了类加载器的工作原理和双亲委派模型。

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

java执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kgTu83Cc-1620714646035)(https://note.youdao.com/yws/res/4568/4411E7E3BA7B4669A89239FF63E71E4F)]

从图中可以看出,java文件先会被编译成.class字节码文件,由jvm的类加载器加载到内存中,通过字节码解释器或即时编译器编译成汇编语言在操作系统上执行

Class File Format

整个class其实就是二进制的字节流,供jvm解析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eyOueAqC-1620714646044)(https://note.youdao.com/yws/res/4630/CC1C1AD306E94DED83D576430ABD90A9)]

整个class文件的构成,基本可以分为几个部分:

  • magic—魔数(不同类型后缀的文件,文件前缀都不一样,称为魔数,.class文件的魔数前缀是CAFE BABE,占4个字节)
  • minor_version:jvm小版本号
  • major_version:jvm大版本,1.8版本主版本是52(0034占两个字节)
  • constant_count:常量池长度
  • access_flag:代表类的声明类型(public、final、abstract等)
  • this_class:表示当前类
  • super_class:表示当前类的父类
  • interfaces_count:实现的接口数量
  • interfaces:接口具体索引
  • fields_count:类有哪些属性数量
  • fields:类有哪些属性
  • methods_count:方法数量
  • methods:方法的索引
  • attributes_count:属性数量
  • attributes:属性表集合,附加项有一项是code,记录类的方法体中的具体代码经过javac编译后的字节码指令

Class Loading Linking Initializing

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4kCOufUk-1620714646047)(https://note.youdao.com/yws/res/4635/D1E59A169DC74FF2BC4CCA6543457E4A)]

类加载的过程主要分为三部分,加载、连接(连接又包括验证、准备、解析)、初始化

加载

本阶段jvm会通过类的完全路径名找到具体的.class字节流文件(用到了双亲委派模型),将字节流文件的内容放入方法区,并转译成方法区可执行的数据结构。之后在内存中生成一个Class对象,作为方法区这个类各种数据结构的访问入口。
想获取Class对象很简单,比如String.class、String.getClass()都可以拿到对应的Class对象。Class对象可以理解为是单例的,针对一个类只会有一个Class对象。
Class对象比较特殊,它也在堆中

连接

  • 验证:校验字节码内容是否正确,格式是否符合JVM规定,比如.class文件开头的魔数是cafe babe,如果有问题会报错
  • 准备:这时候给类变量分配内存(static修饰的变量)和默认值,这里的默认值是0值。例如类中变量定义为
public static int value = 123;

准备阶段赋的初始值是0,value的值真正变为123是在之后的初始化阶段

  • 解析:将加载到常量池的类的各种符号引用替换为直接引用

初始化

类加载的最后一步,真正执行类中定义的程序代码,静态变量赋值(上述value会真正赋值为123,执行静态块的代码)

主要是执行类构造器()方法的过程,给静态变量赋初始值。具体描述可参考“深入理解Java虚拟机”第七章225页内容

类加载器和双亲委派模型(加载阶段)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xj2Fjde6-1620714646050)(https://note.youdao.com/yws/res/4639/5066C66418604BD4BBFE5EE3FD522D50)]

类加载器分为四种:Bootstrap类加载器(启动类加载器)、扩展类加载器、应用程序类加载器和自定义加载器。java中只能拿到除启动类加载器之外的其他类加载器,因为启动类加载器是用C++实现的。可查看Launcher类确定不同类加载器的加载范围(类加载器是Launcher的内部类)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W63i7FHR-1620714646052)(https://note.youdao.com/yws/res/4755/66AFED39518344378E976E06857117BE)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ER1OWYtw-1620714646054)(https://note.youdao.com/yws/res/4759/D7B095EB404149B29D024C1D4900048D)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RJRyn4D9-1620714646055)(https://note.youdao.com/yws/res/4757/FB7BF0BF0CBB439D878F8534EF06C358)]

加载过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5kGNk9bJ-1620714646057)(https://note.youdao.com/yws/res/4764/4D04144C991942B182DAC9F7EABBE887)]

为什么使用双亲委派模型?

为了安全。即防止内存中出现多份同样的字节码。
从反向思考这个问题,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,多个类加载器都去加载这个类到内存中,系统中将会出现多个不同的Object类,那么类之间的比较结果及类的唯一性将无法保证,而且如果不使用这种双亲委派模型将会给虚拟机的安全带来隐患。所以,要让类对象进行比较有意义,前提是他们要被同一个类加载器加载。

打破双亲委派模型在什么时候发生

  • 实现:重写loadClass()
  • 何时打破
    • jdk1.2之前,自定义类加载器都必须重写loadClass
    • ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
    • 热启动、热部署
      • osgi tomcat 都有自己的模块指定classLoader(可以加载同一类库不同版本)

自定义类加载器(加载阶段)

类加载器重要方法

  • loadClass(“类全路径名”)—加载类
  • findLoadedClass(“类全路径名”)—类加载器查询维护的表,判断该类是否加载过
  • findClass(“类全路径名”)—查找类文件并加载到内存,是自定义类加载器需要重写的方法

相关源码

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

自定义类加载器只需继承ClassLoader类,实现findClass方法即可(采用模板方法模式,可能用到方法defineClass)。

扩展

有些对class编译文件要求安全性的场景,可对编译后的class文件进行加密,之后自定义类加载器,重写findClass方法中进行解密之后加载进内存

如果有多个自定义类加载器,如何指定父加载器?可通过构造方法。默认是应用程序类加载器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QQeTQtXH-1620714646059)(https://note.youdao.com/yws/res/4845/1A320D4399E643D7866DD24D3A2FC6F9)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4RTYsJ81-1620714646064)(https://note.youdao.com/yws/res/4849/9473A66973D94310A0785644F9B2C121)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JlDjltbd-1620714646065)(https://note.youdao.com/yws/res/4851/5AF1F17F6427496AB09C028C04450DE9)]

lazyloading(加载阶段)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lMTE8gq3-1620714646068)(https://note.youdao.com/yws/res/4799/A4BBADE9A0C34017BCE057BE38D2E6B0)]

示例

public static class P {
		final static int i = 8;
		static int j = 9;
		static {
			System.out.println("P");
		}
	}

public static class X extends P {
	static {
		System.out.println("X");
	}
}

public static void main(String[] args) {
	//1
	P p;
	//2
	X x = new X();
	//3
	System.out.println(P.i);
	//4
	System.out.println(P.j);
}
  1. 不会打印"P",“X”
  2. 会打印"P",“X”
  3. 不会打印"P",“X”
  4. 会打印"P"

Jvm的编译(混合编译、解释编译、即时编译)(加载阶段)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q9q0vboz-1620714646070)(https://note.youdao.com/yws/res/4820/77DD10B1F481401AB38A98290897F5C6)]

检测热点代码
-XX:CompileThreshold = 10000

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值