资讯专栏INFORMATION COLUMN

JAVA类加载机制全解析

tomener / 426人阅读

摘要:当程序使用某个类时,如果该类还没被初始化,加载到内存中,则系统会通过加载连接初始化三个过程来对该类进行初始化。一旦一个类被加载到中之后,就不会再次载入了。它既可以从本地文件系统获取二进制文件来加载类,也可以远程主机获取二进制文件来加载类。

当程序使用某个类时,如果该类还没被初始化,加载到内存中,则系统会通过加载、连接、初始化三个过程来对该类进行初始化。该过程就被称为类的初始化

类加载

指将类的class文件读入内存,并为之创建一个java.lang.Class的对象

类文件来源

从本地文件系统加载的class文件

从JAR包加载class文件

从网络加载class文件

把一个Java源文件动态编译,并执行加载

类加载器通常无须等到“首次使用”该类时才加载该类,JVM允许系统预先加载某些类

类加载器

类加载器就是负责加载所有的类,将其载入内存中,生成一个java.lang.Class实例。一旦一个类被加载到JVM中之后,就不会再次载入了。

根类加载器(Bootstrap ClassLoader):其负责加载Java的核心类,比如String、System这些类

拓展类加载器(Extension ClassLoader):其负责加载JRE的拓展类库

系统类加载器(System ClassLoader):其负责加载CLASSPATH环境变量所指定的JAR包和类路径

用户类加载器:用户自定义的加载器,以类加载器为父类

类加载器之间的父子关系并不是继承关系,是类加载器实例之间的关系

    public static void main(String[] args) throws IOException {
        ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系统类加载");
        Enumeration em1 = systemLoader.getResources("");
        while (em1.hasMoreElements()) {
            System.out.println(em1.nextElement());
        }
        ClassLoader extensionLader = systemLoader.getParent();
        System.out.println("拓展类加载器" + extensionLader);
        System.out.println("拓展类加载器的父" + extensionLader.getParent());
    }

结果

系统类加载
file:/E:/gaode/em/bin/
拓展类加载器sun.misc.Launcher$ExtClassLoader@6d06d69c
拓展类加载器的父null

为什么根类加载器为NULL?

根类加载器并不是Java实现的,而且由于程序通常须访问根加载器,因此访问扩展类加载器的父类加载器时返回NULL

JVM类加载机制

全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

URLClassLoader类

URLClassLoader为ClassLoader的一个实现类,该类也是系统类加载器和拓展类加载器的父类(继承关系)。它既可以从本地文件系统获取二进制文件来加载类,也可以远程主机获取二进制文件来加载类。

两个构造器

URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的路径来查询并加载类

URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父类加载器创建一个ClassLoader对象,其他功能与前一个构造器相同

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import com.mysql.jdbc.Driver;

public class GetMysql {
    private static Connection conn;
    public static Connection getConn(String url,String user,String pass) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{
        if(conn==null){
            URL[]urls={new URL("file:mysql-connector-java-5.1.18.jar")};
            URLClassLoader myClassLoader=new URLClassLoader(urls);
            Driver driver=(Driver) myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance();
            Properties pros=new Properties();
            pros.setProperty("user", user);
            pros.setProperty("password", pass);
            conn=driver.connect(url, pros);
        }
        return conn;
    }
    public static method1 getConn() throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{
        
            URL[]urls={new URL("file:com.em")};
            URLClassLoader myClassLoader=new URLClassLoader(urls);
            method1 driver=(method1) myClassLoader.loadClass("com.em.method1").newInstance();
        
        return driver;
    }
    
    public static void main(String[] args) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException {
        System.out.println(getConn("jdbc:mysql://10.10.16.11:3306/auto?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true", "jiji", "jiji"));
        System.out.println(getConn());
    }
}

获得URLClassLoader对象后,调用loanClass()方法来加载指定的类

自定义类加载器
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;

public class CompileClassLoader extends ClassLoader

{

    // 读取一个文件的内容

    @SuppressWarnings("resource")
    private byte[] getBytes(String filename) throws IOException

    {

        File file = new File(filename);

        long len = file.length();

        byte[] raw = new byte[(int) len];

        FileInputStream fin = new FileInputStream(file);

        // 一次读取class文件的全部二进制数据

        int r = fin.read(raw);

        if (r != len)

            throw new IOException("无法读取全部文件" + r + "!=" + len);

        fin.close();
        return raw;

    }

    // 定义编译指定java文件的方法

    private boolean compile(String javaFile) throws IOException

    {

        System.out.println("CompileClassLoader:正在编译" + javaFile + "……..");

        // 调用系统的javac命令

        Process p = Runtime.getRuntime().exec("javac" + javaFile);

        try {

            // 其它线程都等待这个线程完成

            p.waitFor();

        } catch (InterruptedException ie)

        {

            System.out.println(ie);

        }

        // 获取javac 的线程的退出值

        int ret = p.exitValue();

        // 返回编译是否成功

        return ret == 0;

    }

    // 重写Classloader的findCLass方法

    protected Class findClass(String name) throws ClassNotFoundException

    {

        Class clazz = null;

        // 将包路径中的.替换成斜线/

        String fileStub = name.replace(".", "/");

        String javaFilename = fileStub + ".java";

        String classFilename = fileStub + ".class";

        File javaFile = new File(javaFilename);

        File classFile = new File(classFilename);

        // 当指定Java源文件存在,且class文件不存在,或者Java源文件的修改时间比class文件//修改时间晚时,重新编译

        if (javaFile.exists() && (!classFile.exists())
                || javaFile.lastModified() > classFile.lastModified())

        {

            try {

                // 如果编译失败,或该Class文件不存在

                if (!compile(javaFilename) || !classFile.exists())

                {

                    throw new ClassNotFoundException("ClassNotFoundException:"
                            + javaFilename);

                }

            } catch (IOException ex)

            {

                ex.printStackTrace();

            }

        }

        // 如果class文件存在,系统负责将该文件转化成class对象

        if (classFile.exists())

        {

            try {

                // 将class文件的二进制数据读入数组

                byte[] raw = getBytes(classFilename);

                // 调用Classloader的defineClass方法将二进制数据转换成class对象

                clazz = defineClass(name, raw, 0, raw.length);

            } catch (IOException ie)

            {

                ie.printStackTrace();

            }

        }

        // 如果claszz为null,表明加载失败,则抛出异常

        if (clazz == null) {

            throw new ClassNotFoundException(name);

        }

        return clazz;

    }

    // 定义一个主方法

    public static void main(String[] args) throws Exception

    {

        // 如果运行该程序时没有参数,即没有目标类

        if (args.length < 1) {

            System.out.println("缺少运行的目标类,请按如下格式运行java源文件:");

            System.out.println("java CompileClassLoader ClassName");

        }

        // 第一个参数是需要运行的类

        String progClass = args[0];

        // 剩下的参数将作为运行目标类时的参数,所以将这些参数复制到一个新数组中

        String progargs[] = new String[args.length - 1];

        System.arraycopy(args, 1, progargs, 0, progargs.length);

        CompileClassLoader cl = new CompileClassLoader();

        // 加载需要运行的类

        Class clazz = cl.loadClass(progClass);

        // 获取需要运行的类的主方法

        Method main = clazz.getMethod("main", (new String[0]).getClass());

        Object argsArray[] = { progargs };

        main.invoke(null, argsArray);

    }

}

JVM中除了根类加载器之外的所有类的加载器都是ClassLoader子类的实例,通过重写ClassLoader中的方法,实现自定义的类加载器

loadClass(String name,boolean resolve):为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取制定累对应的Class对象

findClass(String name):根据指定名称来查找类

推荐使用findClass方法

类的链接

当类被加载后,系统会为之生成一个Class对象,接着将会进入连接阶段,链接阶段负责把类的二进制数据合并到JRE中

三个阶段

验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致

准备:负责为类的类变量分配内存。并设置默认初始值

解析:将类的二进制数据中的符号引用替换成直接引用

类的初始化

JVM负责对类进行初始化,主要对类变量进行初始化

在Java中对类变量进行初始值设定有两种方式:①声明类变量是指定初始值②使用静态代码块为类变量指定初始值

JVM初始化步骤

假如这个类还没有被加载和连接,则程序先加载并连接该类

假如该类的直接父类还没有被初始化,则先初始化其直接父类

假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机

创建类实例。也就是new的方式

调用某个类的类方法

访问某个类或接口的类变量,或为该类变量赋值

使用反射方式强制创建某个类或接口对应的java.lang.Class对象

初始化某个类的子类,则其父类也会被初始化

直接使用java.exe命令来运行某个主类

类加载机制(类加载过程和类加载器)

更多内容可以关注微信公众号,或者访问AppZone网站

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66010.html

相关文章

  • Java加载机制

    摘要:如果需要支持类的动态加载或需要对编译后的字节码文件进行解密操作等,就需要与类加载器打交道了。双亲委派模型,双亲委派模型,约定类加载器的加载机制。任何之类的字节码都无法调用方法,因为该方法只能在类加载的过程中由调用。 jvm系列 垃圾回收基础 JVM的编译策略 GC的三大基础算法 GC的三大高级算法 GC策略的评价指标 JVM信息查看 GC通用日志解读 jvm的card table数据...

    aervon 评论0 收藏0
  • JAVA 虚拟机加载机制和字节码执行引擎

    摘要:实现这个口号的就是可以运行在不同平台上的虚拟机和与平台无关的字节码。类加载过程加载加载是类加载的第一个阶段,虚拟机要完成以下三个过程通过类的全限定名获取定义此类的二进制字节流。验证目的是确保文件字节流信息符合虚拟机的要求。 引言 我们知道java代码编译后生成的是字节码,那虚拟机是如何加载这些class字节码文件的呢?加载之后又是如何进行方法调用的呢? 一 类文件结构 无关性基石 ja...

    RichardXG 评论0 收藏0
  • Java加载机制

    摘要:类加载器类加载器执行的操作就是上述加载阶段做的事,通过一个类的全限定名来获取定义这个类的二进制字节流,类加载器可以分为下列三种。应用程序类加载器,也称为系统类加载器。 类加载流程: showImg(https://segmentfault.com/img/bV8SRP?w=1152&h=388);从上面这幅图可以看出一个类从加载到卸载有7个阶段,其中验证、准备和解析这三个步骤统称为连接...

    missonce 评论0 收藏0
  • 虚拟机加载机制

    摘要:加载阶段在类的加载阶段,虚拟机需要完成以下件事情通过一个类的全限定名来获取定义此类的二进制字节流。验证阶段验证是连接阶段的第一步,这一阶段的目的是为了确保文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 注:本篇文章中的内容是根据《深入理解Java虚拟机--JVM高级特性与最佳实践》而总结的,如有理解错误,欢迎大家指正! 虚拟机把描述类的数据从Class文件...

    k00baa 评论0 收藏0
  • java加载机制

    摘要:在加载阶段,虚拟机要完成件事情通过一个类的全限定名来获取定义此类的二进制字节流。前面的阶段中,除了加载的时候,可以由用户指定自定义类加载器之外,别的都是由虚拟机主导控制。 java类加载机制 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,确实编程语言发展的一大步 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直...

    garfileo 评论0 收藏0

发表评论

0条评论

tomener

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<