VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 数据库 > Oracle教程 >
  • JVM简明笔记3:类加载机制

1 类的加载

类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。

类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载器并不需要等到某个类被首次主动使用时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了 .class 文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

加载 .class 文件的方式:

  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip,jar等归档文件中加载 .class 文件
  • 从专有数据库中提取 .class 文件
  • 将Java源文件动态编译为 .class 文件

执行顺序

  • 类加载子系统负责从文件系统或是网络中加载 .class 文件,class文件在文件开头有特定的文件标识。
  • 把加载后的class类信息存放于方法区,除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
  • ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定;
  • 如果调用构造器实例化对象,则该对象存放在堆区;

2 类的生命周期

  • 加载(Loading)
  • 连接(Linking)
    • 验证(Verification)
    • 准备(Preparation)
    • 解析(Resolution)
  • 初始化(Initialization)
  • 使用(Using)
  • 卸载(Unloading)

加载阶段(Loading)

此阶段用于查到相应的类(通过类名进行查找)并将此类的字节流转换为方法区运行时的数据结构,然后再在内存中生成一个能代表此类的 java.lang.Class 对象,作为其他数据访问的入口。

(需要注意的是加载阶段和连接阶段的部分动作有可能是交叉执行的,比如一部分字节码文件格式的验证,在加载阶段还未完成时就已经开始验证了。)

  • 预加载:虚拟机启动时加载,加载的是 JAVA_HOME/lib/ 下的 rt.jar 下的 .class 文件 (可以写一个空的 main 函数,设置虚拟机参数为 -XX:+TraceClassLoading 来获取类加载信息)
  • 运行时加载:虚拟机在用到一个 .class 文件的时候,会先去内存中查看一下这个 .class 文件有没有被加载,如果没有就会按照类的全限定名来加载这个类。
    • 获取 .class 文件的二进制流 (没有规定二进制字节流要必须从哪里来或者怎么来,所以留下了可扩展的空间)
    • 将类信息、静态变量、字节码、常量这些 .class 文件中的内容放入方法区中
    • 在内存中生成一个代表这个 .class 文件的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。一般这个Class是在堆里的,不过HotSpot虚拟机比较特殊,这个Class对象是放在方法区中的

验证阶段(Verification)

此步骤主要是为了验证字节码的安全性,如果不做安全校验的话可能会载入非安全或有错误的字节码,从而导致系统崩溃,它是 JVM 自我保护的一项重要举措。

验证的主要动作大概有以下几个:

  • 文件格式校验包括常量池中的常量类型、Class 文件的各个部分是否被删除或被追加了其他信息等;
  • 元数据校验包括父类正确性校验(检查父类是否有被 final 修饰)、抽象类校验等;
  • 字节码校验,此步骤最为关键和复杂,主要用于校验程序中的语义是否合法且符合逻辑;
  • 符号引用校验,对类自身以外比如常量池中的各种符号引用的信息进行匹配性校验。

准备阶段(Preparation)

此阶段是用来初始化并为类中定义的静态变量分配内存的,这些静态变量会被分配到方法区上。

HotSpot 虚拟机在 JDK 1.7 之前都在方法区,而 JDK 1.8 之后此变量会随着类对象一起存放到 Java 堆中。

  • 这时候进行内存分配的仅仅是类变量(被static修饰的变量),而不是实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中
  • 这个阶段赋初始值的变量指的是那些不被 final 修饰的 static 变量
    • 比如 public static int value = 123,在准备阶段过后是 0 而不是 123 ,给 value 赋值为123的动作将在初始化阶段才进行
    • 比如 public static final int value = 123; ,在准备阶段,虚拟机就会给 value 赋值为123。

解析阶段(Resolution)

此阶段主要是用来解析类、接口、字段及方法的,解析时会把符号引用替换成直接引用。

  • 符号引用:是指以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;
  • 直接引用:是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。

符号引用和直接引用有一个重要的区别:使用符号引用时被引用的目标不一定已经加载到内存中;而使用直接引用时,引用的目标必定已经存在虚拟机的内存中了。

初始化(Initialization)

初始化阶段 JVM 就正式开始执行类中编写的 Java 业务代码了。到这一步骤之后,类的加载过程就算正式完成了。

3 Java类加载器

  • 启动类加载器(BootStrapClassLoader):C/C++ 实现
  • 其他类加载器(Extension Class Loader、System Class Loader、User-Defined ClassLoader):Java 实现,规范定义自定义加载器是指派生于抽象类 ClassLoder 的类加载器

启动类加载器(Bootstrap ClassLoader)

C/C++ 实现,嵌套在 JVM 中,并不继承自 java.lang.ClassLoader,没有父加载器。用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar、resource.jar 或 sun.boot.class.path 路径下的内容),用于提供JVM自身需要的类。

扩展类加载器(Extension ClassLoader)

Java 实现,派生于 ClassLoader。由 sun.misc.Launcher$ExtClassLoader 实现。用来从 java.ext.dirs 系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext 子目录(扩展目录)下加载类库。如果用户创建的JAR 放在此目录下,也会自动由扩展类加载器加载。

应用类加载器(Application ClassLoader),又称系统类加载器

Java 实现,派生于 ClassLoader,父类加载器为扩展类加载器。由 sun.misc.Lanucher$AppClassLoader 实现。程序中默认的类加载器,一般来说,Java 应用的类都是由它来完成加载的,它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库。通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。

用户自定义类加载器(User-Defined ClassLoader)

在日常的 Java 开发中,类加载几乎是由三种加载器配合执行的,在必要时我们还可以自定义类加载器,来定制类的加载方式。

类加载器包含示意图

ClassLoader 相关类图

重点关注 ClassLoader、ExtClassLoader、AppClassLoader。

4 双亲委派模型

过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即 ClassNotFoundException ),子加载器才会尝试自己去加载。

好处:使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。同时也避免了多份同样字节码的加载。

Java 中实现双亲委派的代码都集中在 java.lang.ClassLoader 的 loadClass() 方法中,简单分析如下:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded  // 1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {                         // 2. 判断一下是否有父加载器,若有交给父加载器加,否则调用 BootstrapClassLoader 加载。
                    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) {                                  // 3. 如果第二步骤依然没有找到指定的类,那么调用当前类加载器的 findClass 方法来完成类加载。
                // 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;
    }
}

// 自定义类加载器就可以重写 finalClass 方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

我们分析下 ExtClassLoader 与 AppClassLoader 源码,这两个类都是 sun.misc.Launcher 类的静态内部类

Launcher 类的初始化

/**
 * This class is used by the system to launch the main application.   Launcher类
 */
public class Launcher {
    private static URLStreamHandlerFactory factory = new Factory();
    private static Launcher launcher = new Launcher();   // 1. Luncher 类加载后会从此静态变量调用到默认的构造方法
    private static String bootClassPath =
        System.getProperty("sun.boot.class.path");

    public static Launcher getLauncher() {
        return launcher;
    }

    private ClassLoader loader;

    public Launcher() {
        // Create the extension class loader
        ClassLoader extcl;
        try {
            extcl = ExtClassLoader.getExtClassLoader();    // 2. 扩展类加载器
        } catch (IOException e) {
            throw new InternalError(
                "Could not create extension class loader", e);
        }

        // Now create the class loader to use to launch the application
        try {
            loader = AppClassLoader.getAppClassLoader(extcl);   // 3. 应用类加载器(系统类加载器)
        } catch (IOException e) {
            throw new InternalError(
                "Could not create application class loader", e);
        }

        // Also set the context class loader for the primordial thread.   // 4. 设置 ContextClassLoader 为应用类加载器(系统类加载器)
        Thread.currentThread().setContextClassLoader(loader);

        // Finally, install a security manager if requested
        String s = System.getProperty("java.security.manager");
        if (s != null) {
            // init FileSystem machinery before SecurityManager installation
            sun.nio.fs.DefaultFileSystemProvider.create();

            SecurityManager sm = null;
            if ("".equals(s) || "default".equals(s)) {
                sm = new java.lang.SecurityManager();
            } else {
                try {
                    sm = (SecurityManager)loader.loadClass(s).newInstance();
                } catch (IllegalAccessException e) {
                } catch (InstantiationException e) {
                } catch (ClassNotFoundException e) {
                } catch (ClassCastException e) {
                }
            }
            if (sm != null) {
                System.setSecurityManager(sm);
            } else {
                throw new InternalError(
                    "Could not create SecurityManager: " + s);
            }
        }
    }

    // ...


    /*
     * The class loader used for loading installed extensions.   关于 ExtClassLoader
     */
    static class ExtClassLoader extends URLClassLoader {

        // ...

        private static ExtClassLoader createExtClassLoader() throws IOException {
            try {
                // Prior implementations of this doPrivileged() block supplied
                // aa synthesized ACC via a call to the private method
                // ExtClassLoader.getContext().

                return AccessController.doPrivileged(
                    new PrivilegedExceptionAction<ExtClassLoader>() {
                        public ExtClassLoader run() throws IOException {
                            final File[] dirs = getExtDirs();                    // 4. 获取扩展类加载器加载的目录文件
                            int len = dirs.length;
                            for (int i = 0; i < len; i++) {
                                MetaIndex.registerDirectory(dirs[i]);
                            }
                            return new ExtClassLoader(dirs);                    // 5. 创建一个扩展类加载器进行返回
                        }
                    });
            } catch (java.security.PrivilegedActionException e) {
                throw (IOException) e.getException();
            }
        }

        void addExtURL(URL url) {
            super.addURL(url);
        }

        /*
         * Creates a new ExtClassLoader for the specified directories.
         */
        public ExtClassLoader(File[] dirs) throws IOException {
            super(getExtURLs(dirs), null, factory);
            SharedSecrets.getJavaNetAccess().
                getURLClassPath(this).initLookupCache(this);
        }

        private static File[] getExtDirs() {
            String s = System.getProperty("java.ext.dirs");   // 1. 将加载变量 java.ext.dirs 的值指示的路径下的类
            File[] dirs;
            if (s != null) {
                StringTokenizer st =
                    new StringTokenizer(s, File.pathSeparator);  // 2. 按照 File.pathSeparator 进行 split
                int count = st.countTokens();
                dirs = new File[count];
                for (int i = 0; i < count; i++) {
                    dirs[i] = new File(st.nextToken());
                }
            } else {
                dirs = new File[0];
            }
            return dirs;                                      // 3. 返回 File 对象数组
        }


        // ...

    }

    /**
     * The class loader used for loading from java.class.path.   关于 AppClassLoader
     * runs in a restricted security context.
     */
    static class AppClassLoader extends URLClassLoader {

        // ...

        public static ClassLoader getAppClassLoader(final ClassLoader extcl)
            throws IOException
        {
            final String s = System.getProperty("java.class.path");                  // 1. 加载 java.class.path
            final File[] path = (s == null) ? new File[0] : getClassPath(s);

            // Note: on bugid 4256530
            // Prior implementations of this doPrivileged() block supplied
            // a rather restrictive ACC via a call to the private method
            // AppClassLoader.getContext(). This proved overly restrictive
            // when loading  classes. Specifically it prevent
            // accessClassInPackage.sun.* grants from being honored.
            //
            return AccessController.doPrivileged(
                new PrivilegedAction<AppClassLoader>() {
                    public AppClassLoader run() {
                    URL[] urls =
                        (s == null) ? new URL[0] : pathToURLs(path);
                    return new AppClassLoader(urls, extcl);
                }
            });
        }

        final URLClassPath ucp;

        /*
         * Creates a new AppClassLoader
         */
        AppClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent, factory);
            ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
            ucp.initLookupCache(this);
        }

        /**
         * Override loadClass so we can checkPackageAccess.   // 0. 重写 loadClass 方法:checkPackageAccess 之后调用 super.loadClass
         */
        public Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            int i = name.lastIndexOf('.');
            if (i != -1) {
                SecurityManager sm = System.getSecurityManager();
                if (sm != null) {
                    sm.checkPackageAccess(name.substring(0, i));
                }
            }

            if (ucp.knownToNotExist(name)) {
                // The class of the given name is not found in the parent
                // class loader as well as its local URLClassPath.
                // Check if this class has already been defined dynamically;
                // if so, return the loaded class; otherwise, skip the parent
                // delegation and findClass.
                Class<?> c = findLoadedClass(name);
                if (c != null) {
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
                throw new ClassNotFoundException(name);
            }

            return (super.loadClass(name, resolve));
        }

        // ...
    }
}

5 自定义类加载器

自定义类加载器的目的:

  • 隔离加载类:模块隔离,把类加载到不同的应用选中。比如 Tomcat 这类 Web 应用服务器,内部自定义了好几中类加载器,用于隔离 Web 应用服务器上的不同应用程序。
  • 修改类加载方式:除了 Bootstrap 加载器外,其他的加载并非一定要引入。根据实际情况在某个时间点按需进行动态加载。
  • 扩展加载源:可以实现从其他途径加载 class 文件。
  • 防止源码泄漏:Java 代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码。

通过对双亲委派模型源码的解读,我们可以分析出两种自定义类加载器的做法:

  • 重写 loadClass 方法:不推荐,因为会破坏双亲委派模型
  • 重写 findClass 方法:推荐

采用 findClass 方法自定义类加载器实现:

public class MyClassLoader extends ClassLoader{

    private String dir;
    public static final String fileType = ".class";

    public MyClassLoader(String dir) {
        this.dir = dir;
    }

    public MyClassLoader(ClassLoader parent, String dir) {
        super(parent);
        this.dir = dir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 定义输入和输出流
        BufferedInputStream bis = null;
        ByteArrayOutputStream baos = null;

        String fileName = dir + name + fileType;
        try {
            bis = new BufferedInputStream(new FileInputStream(fileName));
            baos = new ByteArrayOutputStream();

            // 读取字节数据
            int len;
            byte[] data = new byte[1024];
            while ((len = bis.read(data)) != -1) {
                baos.write(data, 0, len);
            }
            byte[] byteCode = baos.toByteArray();

            // 字节数组转为Class对象
            Class<?> definedClass = defineClass(null, byteCode, 0, byteCode.length);
            return definedClass;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }
}

测试

public class MyClassLoaderTest {

    public static void main(String[] args) {
        MyClassLoader classLoader = new MyClassLoader("D:/");
        try {
            Class<?> dogClass = classLoader.loadClass("Test");
            System.out.println(dogClass.getClassLoader().getClass().getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

内容为之前学习笔记整理,如果有问题请指正!

 

来源:

https://www.cnblogs.com/linweiwang/p/15496765.html

相关教程