jvm类加载机制

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

类加载

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

类文件来源
  • 从本地文件系统加载的class文件
  • 从JAR包加载class文件
  • 从网络加载class文件
  • 把一个Java源文件动态编译,并执行加载

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

类加载器

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

img
img
  • 根类加载器(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<URL> 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初始化步骤

  1. 假如这个类还没有被加载和连接,则程序先加载并连接该类
  2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类
  3. 假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机

  1. 创建类实例。也就是new的方式
  2. 调用某个类的类方法
  3. 访问某个类或接口的类变量,或为该类变量赋值
  4. 使用反射方式强制创建某个类或接口对应的java.lang.Class对象
  5. 初始化某个类的子类,则其父类也会被初始化
  6. 直接使用java.exe命令来运行某个主类
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容