JVM学习笔记(二)jvm类加载机制

来源:互联网 发布:手机测量软件安卓版 编辑:程序博客网 时间:2024/05/21 05:06

类加载过程

  虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
  类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称链接。
这里写图片描述
加载(装载)、验证、准备、初始化和卸载这五个阶段顺序是固定的,类的加载过程必须按照这种顺序开始,而解析阶段不一定;它在某些情况下可以在初始化之后再开始,这是为了运行时动态绑定特性。值得注意的是:这些阶段通常都是互相交叉的混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段。

加载

  • 获得类的二进制字节流

  • 将这个字节流所代表的静态存储结构转为方法区的运行时数据结构

  • 在Java堆中生成对应的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

验证

验证是链接阶段的第一步,这一步主要的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。

验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。

1.文件格式验证

  • 是否以魔术0xCAFEBABE开头

  • 主、次版本号是否在当前虚拟机处理范围之内

2.元数据验证

保证起字节码描述的信息符合java语言规范要求。

  • 是否有父类

  • 是否继承了不允许被继承的类(被final修饰的)

  • 非抽象类实现了所有的抽象方法

3.字节码验证

  • 栈数据类型和操作码数据参数吻合(栈里放的是int类型,使用时却按long类型来加载入本地变量表)

  • 跳转指令跳转到合理的位置

  • 方法体中的类型转换有效,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但不能把一个父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系的、完全不想干的一个数据类型。

4.符号引用验证
 确保解析动作能正常执行

  • 常量池中描述类是否存在

  • 访问的方法和字段是否存在且有足够的权限(private,protected,public,default)

准备

为类变量分配内存(方法区中分 配),并为类变量设置初始值。

  • public static int value = 1;

  • 在准备阶段中, v会被设置为0

  • 在初始化的< clinit >()中才会被设置为1

  • 对于static final类型,在准备阶段就会被赋上正确的值

  • public static final int value = 1

解析

将常量池中的符号引用替换为直接引用

  • 符号引用:字符串,引用对象不一定被加载
  • 直接引用:指针或者地址偏移量,引用对象一定在内存

初始化

  • 执行类构造器< clinit >方法由下面两部分合成:
      – static变量的赋值语句
      – static{}块
//静态语句块中只能访问定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中只能赋值不能访问public class Test{    static{        i = 0;  //给变量赋值可以正常编译通过        System.out.println(i);  //提示“非法向前引用”    }    static int i = 1;}
  • 子类的< clinit >调用前保证父类的< clinit >方法已经执行完毕
     – 虚拟机中第一个被执行< clinit >方法的类是java.lang.Object
  • 一个类的< clinit >方法是线程安全的

类加载器

  • ClassLoader是一个抽象类

  • ClassLoader的实例将读入Java字节码将类装载到JVM中

  • ClassLoader可以定制,满足不同的字节码流获取方式

  • ClassLoader负责类装载过程中的加载阶段

package com.kelly.classloader;import java.io.IOException;import java.io.InputStream;public class ClassLoaderTest {    public static void main(String[] args) throws Exception {        ClassLoader myLoader = new ClassLoader() {            @Override            public Class<?> loadClass(String name) throws ClassNotFoundException {                try {                    String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";                    InputStream is = getClass().getResourceAsStream(filename);                    if (is == null)                        return super.loadClass(name);                    byte[] b = new byte[is.available()];                    is.read(b);                    return defineClass(name, b, 0, b.length);                } catch (IOException e) {                    throw new ClassNotFoundException(name);                }            }        };        Object obj = myLoader.loadClass("com.kelly.classloader.ClassLoaderTest").newInstance();        System.out.println(obj.getClass());        System.out.println(obj instanceof com.kelly.classloader.ClassLoaderTest);    }}

上面代码输出结果;

class com.kelly.classloader.ClassLoaderTestfalse

因为虚拟机中存在两个ClassLoaderTest类,一个由系统应用程序类加载器加载的,另外一个是我们自定义的类加载器加载的。

双亲委派模型

类加载器分类

启动类加载器(Bootstrap ClassLoader)

  这个类加载器负责将< JAVA_HOME >\lib目录中的,或者被 -Xbootclasspath参数所指定的路径中,并且被虚拟机所识别的类库加载到虚拟机内存中。开发者不能直接使用启动类加载器。

扩展类加载器(Extension ClassLoader)

  负责加载< JAVA_HOME >\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。开发者可以直接使用类加载器。

应用程序类加载器

负责加载用户路径上(ClassPath)上所指定的类库。默认的类加载器。

这里写图片描述
             图2. 类加载器双亲委派模型

双亲委派模型的代码都集中在java.lang.ClassLoader的loadClass()方法中,如下代码

protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {        synchronized (getClassLoadingLock(name)) {            // 首先检查请求的类是否被加载            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                    // 说明父类加载器无法完成加载请求                }                if (c == null) {                   //如果父类加载器无法完成加载                   //再调用自身的findClass方法来进行类加载                    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;        }    }

破坏双亲委派模型

  • 优点:对于通用类,各个类加载器去加载都会是同一个类。(比如Object类)。

  • 缺点:顶层的ClassLoader,无法加载底层ClassLoader的类。

  • 思考一个问题:java框架(rt.jar类)如何加载应用的类? javax.xml.parsers包中定义了xml解析的类接口Service Provider Interface (SPI) 位于rt.jar 即接口在启动ClassLoader中。而SPI的实现类,在AppLoader。但启动类记载其不可能认识这些实现类的代码,怎么办呢?

  • 解决:线程上下文加载器(Thread Context ClassLoader)。通过Thread.setContextClassLoader()方法进行设置。它的基本思想是在顶层ClassLoader中,传入底层ClassLoader的实例。

//设置类加载器contextClassLoader public void setContextClassLoader(ClassLoader cl) {        SecurityManager sm = System.getSecurityManager();        if (sm != null) {            sm.checkPermission(new RuntimePermission("setContextClassLoader"));        }        contextClassLoader = cl;    }

代码来自javax.xml.parsers.FactoryFinder展示如何在启动类加载器中加载AppLoader的类

static private Class<?> getProviderClass(String className, ClassLoader cl,            boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException    {        try {            if (cl == null) {                if (useBSClsLoader) {//使用bootstrap classLoader加载这个类                    return Class.forName(className, false, FactoryFinder.class.getClassLoader());                } else {                    cl = ss.getContextClassLoader();                    if (cl == null) {                        throw new ClassNotFoundException();                    }                    else {                        return Class.forName(className, false, cl);//使用上下文加载器去加载这个类                    }                }            }            else {//使用当前提供的类加载器去加载这个类                return Class.forName(className, false, cl);            }        }        catch (ClassNotFoundException e1) {            if (doFallback) {                // Use current class loader - should always be bootstrap CL                return Class.forName(className, false, FactoryFinder.class.getClassLoader());            }            else {                throw e1;            }        }    }

双亲委派模型是默认的模式,并不是必须要这么做。Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent。OSGi的ClassLoader形成网状结构,根据需要自由加载Class。

原创粉丝点击