类加载过程
加载, 验证, 准备, 解析, 初始化下面依次说说
加载阶段
- 三步
- 找到class文件
- class文件转变的静态结构保存在方法区
- 生成class对象,保存在方法区
- 由ClassLoader实现类加载
- 启动类加载器 boostrapClassLoder
- %JAVAHOME%/lib
- 扩展类加载器 ExtantionClassLoder
- %JAVAHOME%/
- 系统/应用类加载器 applicationClassLoder
- classpath
- 继承系统类加载器的自定义加载器
- findclass()
- 启动类加载器 boostrapClassLoder
- 双亲委派模式
- 从下往上找是否存在class对象
- 从上往下加载class对象
- 思考
- JDBC、Tomcat为什么要破坏双亲委派
- 自定义类加载器+加密+解密
验证
- 主要是对类元数据等信息结构的校验
准备
- 对类实例赋零值
- 注意final static是在编译器编译的时候就应经进入了常量池中,。
解析
解析阶段常量池中的方法的符号引用直接解析成直接引用
解析过程之字段解析(字段查找过程)
该字段的字段表的Constant_Class_info常量指向的类/接口C
- 在字段查找的时候, 先在本身的类中查找, 如果没有, 从接口中查找(从最先实现开始, 从下往上), 如果接口中没有, 或者没有实现接口, 从继承的父类中查找(从最先实现开始, 从下往上)

解析过程之方法解析(方法的查找)
分派
静态分派
非虚方法
在解析阶段中可以事先确定唯一的调用版本有静态方法、私有方法、实例构造器、父类方法4类
final方法
一个类的方法重载
重载是根据方法的静态参数类型区分的而不是实例参数, 所以在选中方法的版本的时候也是根据方法的静态参数, 而非实例参数.
demo
/** * 重载是根据方法的静态参数类型区分的而不是实例参数, 所以在选中方法的版本的时候也是根据方法的静态参数, 而非实例参数. */ public class StaticDispatch { static abstract class Human { } static class Man extends Human { } static class Woman extends Human { } public void sayHello(Human guy) { System.out.println("hello,guy!"); } public void sayHello(Man guy) { System.out.println("hello,gentleman!"); } public void sayHello(Woman guy) { System.out.println("hello,lady!"); } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); StaticDispatch sr = new StaticDispatch(); sr.sayHello(man); sr.sayHello(woman); //运行结果是: //hello,guy //hello,guy } }
如果实参类型没有直接对应匹配的方法,则实参会向上逐步类型提升,可见变长参数(数组或者…)的重载优先级是最低的, 直到找到方法
import java.io.Serializable; /** * 如果实参类型没有直接对应匹配的方法,则实参会向上逐步类型提升,可见变长参数(数组或者...)的重载优先级是最低的, 直到找到方法 */ public class OverloadTest { // public static void sayHello(char arg) { // // System.out.println("hello char"); // | // // | // } // | // | public static void sayHello(int arg) { // | System.out.println("hello int"); // | // ╲↓╱ }//代码我给排序了,类型自动提升过程 char,int,long,Character,Serializable,Object,char [] public static void sayHello(long arg) { System.out.println("hello long"); } public static void sayHello(Character arg) { System.out.println("hello Character"); } public static void sayHello(Serializable arg) { System.out.println("hello Serializable"); } public static void sayHello(Object arg) { System.out.println("hello Object"); } public static void sayHello(char... arg) { System.out.println("hello char……"); } public static void main(String[] args) { sayHello('a'); // 运行结果为hello int //代码我给排序了,不要误解为从上往下查找哈,当找不到的情况下,实参会自动类型提示,这个查了类型自动提升过程为 char,int,long,Character,Serializable,Object,char [] } }
动态分派
- 虚方法
- 指向常量池中的方法的符号引用
- 虚方法
初始化
类的加载、验证、准备、初始化是先后开始的, 多是在初始化之前,加载,验证,准备必然是已经开始了。
初始化阶段(Cinit<>)对static类变量赋初始值(非零值)
初始化的5个时机
- new / putstatic(调用类的static方法或static字段(非final static)的时候)
- 子类初始化的时候如果父类没有初始化, 那么父类会初始化
- main入口方法对应的类
- 反射
- …忘记了
3种容易误以为会初始化的情况
- 子类.父类static 属性
- 这种情况只会初始化父类, 不会初始化子类
- 数组元素是类类型,那么这种类类型不会初始化, 除非实例化数组元素
- 访问类的final static属性。 那么这个类是不会初始化的, 因为final static在编译阶段就会直接保存到常量池中, 如果用到final static属性, 也只是宏替代。
- 子类.父类static 属性
本文含有隐藏内容,请 开通VIP 后查看