加载过程
- Loading
1、双亲委派
为什么要做双亲委派:主要是出于安全考虑。 举个例子如果没有双亲委派,我们自定义一个类加载器,再定义一个类名字为
java.lang.String,直接把oracle定义的String类覆盖,并把它交给我们自定义的类加载器,让它去加载。我们把自定义的类加载器和java.lang.String打包为一个jar,交给客户。这时,我们就可以动手脚获取用户的敏感信息了。显然这是不安全的。
双亲委派加载过程: 如果我们自定义了类加载器,那么当一个类被加载的时候,首先会交给Custom ClassLoader,他去他的缓存中去寻找是否有该类,没有就交给父加载器App,App拿到这个类之后同样去缓存中去寻找是否有该类 … … 以此类推直到顶层Bootstrap,如果他在缓存没找到该类,那么他会尝试去加载这个类,看这个类是不是rt.jar等核心类,不是的话则交给Extension,Extension拿到这个类,去看这个类是不是jre/lib/ext/*.jar,不是的话继续向下委托,如果直到Custom ClassLoader都没找到这个类,那么直接抛NPE异常,找到就返回。
2、LazyLoading
(1)new getstatic putstatic invokestatic指令,访问final变量除外
(2)java.lang.reflect对类进行反射调用时
(3)初始化子类的时候,父类首先初始化
(4)虚拟机启动时,被执行的主类必须初始化
(5)动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化
3、自定义类加载器:通过观看源码可以知道,ClassLoader采用了钩子函数,模版方法设计模式,因此我们只需要继承ClassLoader,重写findclass方法就可以了。
4、java是解释编译混合性语言,java有自己的解释器Interpreter,以及即时编译器JIT。一个程序在运行时,先进行解释执行,执行过程中它会对代码进行热点检测,对于方法有一个方法计数器,对于循环有一个循环计数器,当方法或者循环执行频率过高时(编译阈值为10000),就会对这段代码进行编译
- linking
1、Verification
验证文件是否符合JVM规定
2、Preparation
为静态成员变量赋默认值
3、Resolution
将类、方法、属性等符号解析为直接引用
常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用。 - Initializing
调用类初始化代码,给静态成员变量赋初始值