JVM之Class加载

load

我们编译后的.class文件是如何被JVM加载进入内存的呢,中间经过了哪些个步骤呢?如下图:
在这里插入图片描述
主要是经过这么几个步骤:

  • loading加载:从磁盘读取xxx.class文件进入内存
  • linking
    - verifiy验证阶段:验证class文件是否已“cafababe”开头
    - prepare准备阶段:对类的成员变量开辟空间,将class中的所有成员变量(静态和非静态)赋值为默认值,不执行任何初始化操作
    - resolute解析阶段:为类、接口、方法、成员变量的符号引用定位直接引用,也即与外部其他class建立引用关系
  • init 初始化:将静态成员变量的默认值替换成初始值

如现有A.class文件,内部定义了一个静态成员变量B=1,其成员变量B加载过程如下:在prepare阶段,先给B赋默认值0,在init阶段,再给B赋初始值1。

那么,当JVM将class文件读取进内存之后,内存中有哪些东西呢?

  1. 将Class元数据信息保存在matespace上(1.8之后,1.8之前是保存在老年代)
类型 保存位置
静态成员变量 方法区的静态部分
静态方法 方法区的静态部分
非静态方法 方法区的非静态部分
静态代码块 方法区的静态部分
构造代码块 方法区的静态部分
  1. 创建当前class文件的Class对象,无论我们后面在代码中new出了多少个实例,JVM中对应的Class对象,始终只有一个。

ClassLoader

Java中有4种ClassLoader,自上而下依次为Bootstrap CLassloder、ExtClassLoader、AppClassLoader、CustomClassLoader(自定义类加载器)

那这4中加载器,它们之间的关系是什么,各自的作用域又是什么呢?要搞清楚这个问题,我们得分析下源码:
Launcher.class部分源码如下:

private static String bootClassPath = System.getProperty("sun.boot.class.path");
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            ...
  }
}

static class ExtClassLoader extends URLClassLoader {
	 private static File[] getExtDirs() {
            String var0 = System.getProperty("java.ext.dirs");
            ...
     }
}

通过源码,我们可以看到有三处定义了uri,这实际便是几个加载器的作用域,即

  • Bootstrap CLassloder, 负责加载sun.boot.class.path路径下的类,如lib/rt.jar,charset.jar等核心类库,此部分功能由c++实现,我们代码中这部分调用getClassLoader()方法,其返回的是个null,是个跟加载器
  • ExtClassLoader ,负责加载java.ext.dirs路径下的类,负责加载扩展包
  • AppClassLoader,负责加载java.class.path路径下的类
  • CustomClassLoader,可自定义加载某个路径下的类以及加载方式。

双亲委派

JVM没有规定一个Class文件具体啥时候需要被加载,具体时间由各自的虚拟机决定。常用的Hotspot采用懒加载方式,即当需要用到这个Class的时候才将该类加载进内存,加载流程是什么样的呢?这里面就会涉及类加载的双亲委派机制了,我们以最全流程为例讲解:
在这里插入图片描述
现有一个Class需要加载进内存,使用CustomClassLoader。

  1. CustomClassLoader查看自身是否有加载过这个Class,如果有则不再加载,直接返回,如果没有加载,转2
  2. CustomClassLoader询问父加载器AppClassLoader是否有加载过这个class,如果父加载器有加载过,直接返回,如果父加载器也没有加载过,转3
  3. AppClassLoader向父加载器ExtClassLoader询问是否有加载过,同理,如果没有加载过,或者加载不成功,转4
  4. ExtClassLoader询问父加载器Bootstrap,结果还是一样没有加载过,且自身不负责加载该类,Bootstrap通知子加载器ExtClassLoader,要求ExtClassLoader自己尝试加载
  5. ExtClassLoader尝试加载该类,发现该类作用域不在自己的管辖内,不能加载,于是再通知自己的子加载器AppClassLoader,要求AppClassLoader自己尝试加载
  6. AppClassLoader加载失败,通知CustomClassLoader自己加载
  7. CustomClassLoader负责加载该class进内存

通过上面的步骤可以得知,所谓双亲委派,就是在需要加载一个新的Class的时候,需要向父加载器一层层的问询是否已经加载过,如果都没有加载过,则由父加载器一层层通知子加载器,由子加载器尝试加载class。

Java为什么要这么设计呢?主要是为了安全,设想这样一种场景,我们自定义了一个加载器,用来加载String类,如果直接走我们自定义的加载器,我们在加载器内部收集所有string数据上传,如果这些数据正好是用户的用户名和密码,那我们就可以轻松拿到每个人的用户名和密码了。
还有一点,为了避免重复加载,如果一个类已经被父加载器加载过了,内存中已经存在了一个Class对象,这时候直接用就可以了,不需要再次加载。

Java是如何实现双亲委派的呢?源码如下:

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;
        }
    }

其实很简单,就是通过递归的方式,先在 parent.loadClass中问询是否有加载过该类,如过parent没有加载过,且尝试加载失败,则当前的子加载器自己尝试加载该class。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章