一个class文件是如何从硬盘到内存,并且开始准备执行。
一共分为三大步:loading->linking->initializing
细化分为:

loading:将class load到内存里面(双亲委派)
verification:校验
preparation:面试可能问-(把class文件赋默认值)
resolution:class文件常量池里面的符号引用转换成直接的内存地址 直接能访问到的内容
initializing:静态变量赋值为初始值

Bootstrap类加载器在获取时会显示null 因为它是C++实现的 Java没有与其对应的class。

一个class文件load到内存中实际有两块内容:
· 把二进制的文件扔到内存中(这块内容在metaSpace(1.8之后)需要看JVM实现)
· 与此同时生成了class类的对象,这个class对象指向了上面那一块内容
Tips:加载器的加载器是不是就是它的parent 不是 不是 不是
parent是加载类的过程中需要查找的一个层次关系,至于parent是被谁加载的是两回事
父加载器不是类加载器的加载器 也不是类加载器的父类加载器

类加载过程:

说到class加载过程就要提到双亲委派机制:
双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程
为什么要有双亲委派:其实是主要为了安全问题 次要是避免再次加载

所有的类加载器都是Launcher的内部类
我们加载类的时候需要先调用loadclass方法,loadclass方法调用了重载的loadClass方法 需不需要解析 把符号引用直接转换为直接访问的地址 并且加载的时候加锁了(sychronized) 这里会检查是不是已经load进来了(findLoadedClass 再往下就是hospot源码了) 如果load进来了直接返回 如果没有load进来 会调用parentload (这就是双亲委派的过程_其实parent也是先findloadedclass 如果找不到也是调用parent 类似于递归调用,这里的parent 是final修饰的 修改不了);
如果都没有找到,父加载器也没有加载成功,只能由我自己去加载,这里面调用了findClass(proctected修饰 只实现了一句话throw ClassNotFoundException ) 只可以在子类去自己去实现。所以自定义classloader只需要重写findClass 即可 这个模式是模板模式方法
这个模式是模板模式方法
什么时候我们需要加载一个类呢?
spring的某个class spring会给你生成动态代理 这个动态代理是一个新的class 当你要用的时候 spring会给你偷偷的load到内存里面
Jrebel热部署 需要一个classloader 将类load内存里面
自定义类加载器:
public class customizeClassLoaderextends ClassLoader {
@Override
protectedClass<?> findClass(String name) throws ClassNotFoundException {
File f = new File("c:/test/", name.replace(".", "/").concat(".class"));
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b=fis.read()) !=0) {
baos.write(b);
}
byte[] bytes = baos.toByteArray();
baos.close();
fis.close();//可以写的更加严谨
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
returnsuper.findClass(name); //throws ClassNotFoundException
}
publicstatic void main(String[] args) throws Exception {
ClassLoaderl = new customizeClassLoader();
Class clazz = l.loadClass("com.test.jvm.Hello");
Class clazz1 = l.loadClass("com.test.jvm.Hello");
System.out.println(clazz == clazz1);
Hello h = (Hello)clazz.newInstance();
h.m();
System.out.println(l.getClass().getClassLoader());
System.out.println(l.getParent());
System.out.println(getSystemClassLoader());
}
}
lazyLoading JVM并没有规定什么时候加载,但是严格规定了什么时候必须初始化
newgetstatic putstatic invokestatic 访问final变量除外
Java.lang.reflect对雷进行反射调用时
初始化子类的时候 父类首先初始化
虚拟机启动的时候 被执行的主类必须初始化
动态语言支持java.lang.invoke.methodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时 该类必须初始化
package com.example.demo.test;
/**
* @ClassName testLazyLoading
* @Description TODO
* @Author zbw
* @Date 4/13/20214:00 PM
**/
public class testLazyLoading { //严格讲应该叫lazy initialzing,因为java虚拟机规范并没有严格规定什么时候必须loading,但严格规定了什么时候initialzing
public static void main(String[] args) throws Exception {
//P p;
//X x = new X();
//System.out.println(P.i);
//System.out.println(P.j);
Class.forName("com.mashibing.jvm.c2_classloader.T008_LazyLoading$P");
}
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");
}
}
}
Class.forName("com.mashibing.jvm.c2_classloader.T008_LazyLoading$P");
这里面的 :由于P是当前类的内部类所以访问他需要外部类++内部类的名字