类加载全过程
当我们使用Java命令启动一个类的main()方法时,首先需要通过类加载器将主类加载到JVM中。
1 | package com.weiba.jvm; |
通过Java命令执行代码的大体流程
loadClass 加载过程
加载
在硬盘上查找并通过IO读取字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等。在加载阶段会在宿主机内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
效验字节码文件的的正确性,如class文件内容依次应该为:CAFEBABE(魔数),副版本号,主版本号,常量池计数器,常量池区域,类信息等等。
Class文件结构参照表
准备
给类的静态变量分配内存,并赋予默认值。
静态变量:基础类型如int为0,boolean为false,对象为null。
静态常量:用字面量进行显示赋值,字面量(保存在常量池中) 可以认为是有确定值的基本数据类型,还有具有确定值的string。
解析
将符号引用(静态方法如main())替换为直接引用(内存地址),该阶段会把一些静态方法替换为指向数据所存内存的指针或句柄。这就是所谓的静态链接(类加载期间完成)过程,动态链接是程序在运行期间将符号引用替换为直接引用。
初始化
- 对类的静态变量初始化为指定的值。
- 执行静态代码块。
方法区
类被加载至方法区后主要包含:运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应Class实例的引用等信息。
类加载器的引用
这个类加载时使用的类加载器。
对应Class实例的引用
类加载器在加载类信息放到方法区后,会创建一个对应的Class类型的对象实例放到堆中,作为开发人员访问方法区中类定义的入口和切入点。
类加载器
注意:同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才能认为他们是同一个。
引导类加载器(BootStrapClassLoader)
负责加载JRE下lib目录的核心类库。
扩展类加载器器(ExtClassLoader)
负责加载JRE下lib目录中ext扩展文件夹中的类库。
应用类加载器(APPClassLoader)
负责加载classPath路径下的类库,主要是加载我们直接写的和导入的依赖包。
自定义类加载器
自己定义的类加载器,自己管理,如Tomcat打破双亲委派的自定义类加载器,实现各个路径下的class隔离。
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
全盘负责委托机制
全盘负责委托机制是指当一个ClassLoader装载一个类时,除非显式的使用另一个ClassLoader,该类所有依赖的及引用的类都使用这个ClassLoader载入。
双亲委派机制
原理
一个类在加载时,会先找这个类加载器的父类,看是否为空,不为空继续它类加载器的父类,直到没有父类。在找到后会先判断有没有加载过这个,找到直接返回,没有找到返回子类加载器查找,直到返回到自己的类加载器,开始真正的加载。
为什么要设计双亲委派机制?
- 沙箱安全机制:自定义的java.lang.String.class类不会被加载,这样可以防止核心类库被篡改。
- 防止类的重复加载:当父类已经加载了该类,子类就没有必要再加载一次了,保证被加载类的唯一性。