JVM基本知识(三)类加载机制

JVM基本知识(三)类加载机制,第1张

JVM基本知识(三)类加载机制

文章目录
  • 类加载机制
    • 一 基础知识介绍
      • 1.1 小实验
      • 1.2 概述
      • 1.3 ClassLoader
        • 1.3.1 基本知识
        • 1.3.2 基本方法
    • 二 类加载器
      • 2.1 分类
      • 2.2双亲委派模型
      • 2.3 类的动态加载
        • 2.3.1 提升应用类加载器级别
        • 2.3.2 SPI机制
      • 2.4 自定义类加载路径
        • 2.4.1 基本结构
        • 2.5.2 方法
        • 2.5.3 基本用法
        • 2.5.4 案例

类加载机制 一 基础知识介绍 1.1 小实验
  • 代码:
package com.Thread;


public class HelloWord {
    public static void main(String[] args) {
        System.out.println("Hello Word!");
    }
}

  • 增加虚拟机参数

  • 运行结构

  • 由此可见,我们的Java虚拟机在实现这个方法之前,还加载了许多其他的类。

1.2 概述
  • 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
  • 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。
  • 加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。
1.3 ClassLoader 1.3.1 基本知识
  • 类加载器是负责加载类的对象。
  • ClassLoader 类是一个抽象类。 给定类的二进制名称,类加载器应该尝试定位或生成构成类定义的数据。 典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的类文件。
  • 每个Class对象都包含reference定义它的ClassLoader的reference。
  • 数组类的类对象不是由类加载器创建的,而是根据 Java 运行时的需要自动创建的。 Class.getClassLoader()返回的数组类的类加载器与其元素类型的类加载器相同; 如果元素类型是原始类型,则数组类没有类加载器。
  • 应用程序实现ClassLoader 的子类,以扩展 Java 虚拟机动态加载类的方式。
  • 安全管理器通常可以使用类加载器来指示安全域。
  • ClassLoader类使用委托模型来搜索类和资源。ClassLoader的每个实例都有一个关联的父类加载器。 当请求查找类或资源时,ClassLoader实例会将类或资源的搜索委托给其父类加载器,然后再尝试查找类或资源本身。 虚拟机的内置类加载器,称为“引导类加载器”,它本身没有父级,但可以作为ClassLoader`实例的父级。
  • 支持并发加载类的类加载器被称为具有并行能力的类加载器,并且需要在类初始化时通过调用ClassLoader.registerAsParallelCapable方法来注册自己。
  • 请注意, ClassLoader类默认注册为具有并行能力。 但是,如果它们具有并行能力,它的子类仍然需要注册自己。 在委托模型不是严格分层的环境中,类加载器需要具有并行能力,否则类加载会导致死锁,因为加载器锁在类加载过程中一直保持着(参见loadClass方法)。
  • 通常,Java 虚拟机以平台相关的方式从本地文件系统加载类。 例如,在 UNIX 系统上,虚拟机从CLASSPATH环境变量定义的目录中加载类。
  • 但是,有些类可能不是来自文件; 它们可能来自其他来源,例如网络,或者它们可以由应用程序构建。 方法defineClass将一个字节数组转换为Class 类的一个实例。 可以使用Class.newInstance创建这个新定义的类的实例。
  • 类加载器创建的对象的方法和构造函数可能会引用其他类。 为了确定所引用的类,Java 虚拟机调用最初创建类的类加载器的loadClass方法。
1.3.2 基本方法

  • 重要方法
public Class loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }




protected final Class defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }



protected Class findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }



 protected final Class findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }
二 类加载器

虚拟机设计团队把类加载阶段中的通过一个类的全限定名来获取描述此类的二进制字节流这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为类加载器。

2.1 分类

JVM提供了4种类加载器,分别是启动类加载器、扩展类加载器和应用程序类加载器、自定义启动类加载器

  • 启动类加载器(Bootstrap ClassLoader)负责加载Java_HOME/lib目录中的类库,或通过-Xbootclasspath参数指定路径中被虚拟机认可的类库。

  • 扩展类加载器(Extension ClassLoader):负责加载libext目录或Java. ext. dirs系统变量指定的路径中的所有类库;

  • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

  • 自定义类加载器:我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。

  • 从我们的小实验可以看出,类的层层加载机制。

2.2双亲委派模型
  • 从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现[插图],是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
  • 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
  • 问题:上层加载器的类无法访问到下层加载器的类
 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 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;
        }
    }
2.3 类的动态加载 2.3.1 提升应用类加载器级别
  • 修改虚拟机参数

执行Java时,添加虚拟机参数-Xbootclasspath/a:path,将类路径配置为Bootstrap等级

2.3.2 SPI机制
  • SPI 全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的meta-INF/services文件夹查找文件,自动加载文件里所定义的类。

  • SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

  • 案列:Mysql驱动

  • 应该说,很多地方都用到了这种机制,比如SpringBoot 自动装配,Dubbo的机制

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class SelectTest {
    public SelectTest() {
    }

    public static void main(String[] args) throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/test";
        Connection conn = DriverManager.getConnection(url, "root", "123456");
        Statement stmt = conn.createStatement();
        System.out.println("创建Statement成功!");
        ResultSet rs = stmt.executeQuery("select bookid, bookname, price from t_book order by bookid");

        while(rs.next()) {
            System.out.println(rs.getInt(1) + "," + rs.getString(2) + "," + rs.getInt("price"));
        }

        rs.close();
        stmt.close();
        conn.close();
    }
}
2.4 自定义类加载路径

Java为ClassLoader提供了一个URLClassLoader实现类,该类也是系统类加载器和扩展类加载器的父类(此处是父类,而不是父类加载器,这里是类与类之间的继承关系),URLClassLoader功能比较强大,它既可以从本地文件系统获取二进制文件来加载类,也可以从远程主机获取二进制文件来加载类。

2.4.1 基本结构
 
//构造器
public URLClassLoader(URL[] urls, ClassLoader parent) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        this.acc = AccessController.getContext();
        ucp = new URLClassPath(urls, acc);
    }

 //构造器	
 URLClassLoader(URL[] urls, ClassLoader parent,
                   AccessControlContext acc) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        this.acc = acc;
        ucp = new URLClassPath(urls, acc);
    }
2.5.2 方法
 public InputStream getResourceAsStream(String name) {}



  protected Class findClass(final String name)
        throws ClassNotFoundException
 {}



public final Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
{}


2.5.3 基本用法
File file = new File(jar文件全路径); 
URL url = file.toURL(); 
URLClassLoader loader = new URLClassLoader(new URL[] { url }); 
Class tidyClazz = loader.loadClass(所需class的含包名的全名);
2.5.4 案例
  • 定义Hello01
package edu.ecnu;

public class Hello {
	public void say()
	{
		System.out.println("Hello 111111"); 
	}
}

  • 定义Hello02
package edu.ecnu;

public class Hello {
	public void say()
	{
		System.out.println("Hello 222222"); 
	}
}

  • 将Hello01与Hello02打成Jar包

  • 编写加载类

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class HelloTest {
    public HelloTest() {
    }

    public static void main(String[] args) throws Exception {
        test1();
    }

    public static void test1() throws Exception {
        URL url = new URL("file:E:/java/source/PMOOC11-03-First/bin/");
        URLClassLoader loader = new URLClassLoader(new URL[]{url});
        Class c = loader.loadClass("edu.ecnu.Hello");
        Method m = c.getMethod("say");
        m.invoke(c.newInstance());
        System.out.println(c.getClassLoader());
        System.out.println(c.getClassLoader().getParent());
    }

    public static void test2() throws Exception {
        URL url = new URL("file:C:/Users/Tom/Desktop/PMOOC11-03-First.jar");
        URLClassLoader loader = new URLClassLoader(new URL[]{url});
        Class c = loader.loadClass("edu.ecnu.Hello");
        Method m = c.getMethod("say");
        m.invoke(c.newInstance());
    }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5671429.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存