类的加载机制(代码片段)

huxipeng huxipeng     2022-10-30     320

关键词:

这一章我们主要是对双亲委派机制进行详细讲解:

前面我们知道类加载有系统自带的3种加载器,也有自定义的加载器,那么这些加载器之间的关系是什么,已经在加载类的时候,谁去加载呢?这节,我们将进行讲解。

一、双亲委派机制

技术分享图片

JVM的ClassLoader采用的是树形结构,除了BootstrapClassLoader以外?每个ClassLoader都会有一个parentClassLoader,用户自定义的ClassLoader默认的parentClassLoader是SystemClassLoader,当然你可以自己指定需要用哪一个ClassLoader的实例,我们来看他的API:

技术分享图片

默认的无参构造方法使用的是SystemClassLoader,你可以通过传入一个ClassLoader的实例来指定他的父类加载器。这里强调一点,很多人认为各个父子类加载器之间是继承关系,这里澄清一下,父子类加载器之间是组合关系,子类类加载器会含有一个parentClassLoader的对象,类加载的时候通常会按照树形结构的原则来进行,也就是说,首先是从parentClassLoader中尝试进行加载,当parent无法进行加载时,再从当前的类加载器进行加载,以此类推。JVM会保证一个类在同一个ClassLoader中只会被加载一次。
ClassLoader抽象类为我们定义了一系列的关键的方法,下来让我们来看一下
1、loadClass方法此方法用来加载指定名字的类,ClassLoader会先从已加载的类中寻找,如果没有,则使用父加载器进行加载,如果加载成功则加载,否则从当前的类加载器中进行加载,如果还没有找到该类的class文件则会抛出异常ClassNotFoundException

 技术分享图片

如果该类需要链接,则通过resolveClass进行链接。

2、defineClass,此方法用来将二进制的字节码转换为Class对象,这个对类的自定义加载非常重要,当然前文我们已经说了,当类的二进制文件被加载到内存之后,要进行语法分析,语义分析等一系列的验证,如果不符合JVM规范,则抛出ClassFormateError错误,如果生成的类名和字节码中的不一致,则抛出NoClassDefFoundException,如果加载的class是受保护的、采用不同的标签名的,或者一java.*开头的,则抛出SecurityException,如果要加载的class在之前已经被加载过,则直接抛出LinkageError。

技术分享图片

3、resolveClass,此方法完成Class的链接,如果链接过则直接返回。当Java开发人员调用Class.forName来获取一个class对象的时候,JVM会从方法栈上寻找第一个ClassLoader,通常也就是执行Class.forName的ClassLoader,并使用这个ClassLoader来加载此类。JVM为了保护加载、执行的类的安全,不允许ClassLoader直接卸载加载了的类,只有JVM才可以卸载,在SUN的JDK中,只有ClassLoader没有 被引用的时候,次ClassLoader加载的类才会被卸载!

技术分享图片

附:JDK中ClassLoader的部分源码

1、 构造函数

技术分享图片
protected ClassLoader(ClassLoader parent) 
    SecurityManager security = System.getSecurityManager();
    if (security != null) 
        security.checkCreateClassLoader();
    
    this.parent = parent;
    initialized = true;

    protected ClassLoader() 
    SecurityManager security = System.getSecurityManager();
    if (security != null) 
        security.checkCreateClassLoader();
    
    this.parent = getSystemClassLoader();
    initialized = true;
    
技术分享图片

2loadClass

技术分享图片
public Class<?> loadClass(String name) throws ClassNotFoundException 
    return loadClass(name, false);
    
    protected synchronized Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) 
        try 
       if (parent != null) 
           c = parent.loadClass(name, false);
        else 
           c = findBootstrapClass0(name);
       
         catch (ClassNotFoundException e) 
            // If still not found, then invoke findClass in order
            // to find the class.
            c = findClass(name);
        
    
    if (resolve) 
        resolveClass(c);
    
    return c;
技术分享图片

类的这种加载机制我们称之为父委托加载机制,父委托机制的优点就是能够提高软件系统的安全性。因为在此机制下,用户自定义的类加载器不可能加载本应该由父加载器加载的可靠类,从而防止不可靠的甚至恶意的代码代替由父类加载器加载的可靠代码。如,java.lang.Object类总是由根类加载器加载的,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类。
被定义的类加载器,而它的父类加载器则被称为初始类加载器。

我们知道java中很可能出现类名相同的类,但是JVM却能正常的加载,是因为我们将相同的类名的类放在了不通的包(package)下面,这个也成为命名空间,每个类加载器都有自己的命名空间,命名空间是由该加载器以及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包名+类名)相同的两个类;在不同的命名空间中,有可能出现类的完整名字相同的两个类。

由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名称是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类之间才能相互访问可见(默认访问级别)的类和成员。假设用户自定义了一个类java.lang.TestCase并由用于自定义的类加载器加载,由于java.lang.TestCase和核心类库java.lang.*由不同的类加载器加载,他们属于不同的运行时包,所以java.lang.TestCase不能访问核心库java.lang包中的包可见成员。

同一个命名空间内的类是相互可见的。

子类加载器的命名空间包含所有父类加载器的命名空间,因此由子类加载器加载的类能看见父类加载器加载的类,相反,由父类加载器加载的类不能看见子类加载器加载的类。如果两个加载器之间没有直接或者间接的父子关系,那么他们各自加载的类互不可见。

二、自定义类加载器 

首先类的双亲委派流程为:

技术分享图片

首先,我们定义一个待加载的普通Java类:Test.java。放在com.pony.cl包下:

技术分享图片
package com.pony.cl;

public class Test 
    public void hello() 
        System.out.println("恩,是的,我是由 " + getClass().getClassLoader().getClass()
                + " 加载进来的");
    
技术分享图片

注意:

如果你是直接在当前项目里面创建,待Test.java编译后,请把Test.class文件拷贝走,再将Test.java删除。因为如果Test.class存放在当前项目中,根据双亲委派模型可知,会通过sun.misc.Launcher$AppClassLoader 类加载器加载。为了让我们自定义的类加载器加载,我们把Test.class文件放入到其他目录。

接下来就是自定义我们的类加载器:

技术分享图片
import java.io.FileInputStream;
import java.lang.reflect.Method;

public class Main 
    static class MyClassLoader extends ClassLoader 
        private String classPath;

        public MyClassLoader(String classPath) 
            this.classPath = classPath;
        

        private byte[] loadByte(String name) throws Exception 
            name = name.replaceAll("\\\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                    + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;

        

        protected Class<?> findClass(String name) throws ClassNotFoundException 
            try 
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
             catch (Exception e) 
                e.printStackTrace();
                throw new ClassNotFoundException();
            
        

    ;

    public static void main(String args[]) throws Exception 
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        Class clazz = classLoader.loadClass("com.pony.cl.Test");
        Object obj = clazz.newInstance();
        Method helloMethod = clazz.getDeclaredMethod("hello", null);
        helloMethod.invoke(obj, null);
    
技术分享图片

注意点:

Object obj = clazz.newInstance();
不能写成:
Test obj = (Test)clazz.newInstance();

如果写成这样会报错,因为当前的这个类是由系统加载器加载,而Test是由自定义加载器加载,那么系统类加载和自定义类的加载器不属于同一个运行时包,这个时候是没有办法直接转换的,只能通过反射的方式去访问,反射是唯一一种可以跨越在不同运行时包的方法。




类的加载机制(代码片段)

一.目标:1.什么是类的加载?2.类的生命周期?3.类加载器是什么?4.双亲委派机制是什么?二.原理(类的加载过程及其最终产品):JVM将class文件字节码文件加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,... 查看详情

类的加载机制(代码片段)

最近有很多同学开始学习Java,都问了我一个很基础的问题:publicclassMethodInvokeTestinti=0;publicstaticvoidmain(String[]args)System.out.println(i);这种情况下为什么不可以输出i。由于自己也才入门Java不久,只是大概记得是因为Java类加载机制的... 查看详情

jvm类的加载机制(代码片段)

 1、什么是类的加载类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于... 查看详情

java类的加载机制(代码片段)

1、什么是类的加载类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区... 查看详情

类的加载机制(代码片段)

...化的时候,静态变量的声明语句以及静态代码块都被看作类的初始化语句,Java虚拟机会按照初始化语句在类文件中的先后顺序来依次加载它们。上图中a的初始化其实经过了四步1、a被初始化为默认值02、a被赋予正确的初始值13、... 查看详情

深入理解java虚拟机——类加载机制(代码片段)

文章目录类加载机制类的生命周期类的加载过程1、加载2、验证3、准备4、解析5、初始化类的初始化时机类加载器类与类加载器类加载器分类双亲委派模型工作过程源码分析双亲委派机制的好处类加载机制类的生命周期一个类型... 查看详情

jvm-01:类加载机制(代码片段)

...的微笑http://www.ityouknow.com/jvm.html   1、什么是类的加载类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内... 查看详情

关于类的加载机制和反射机制只看这一篇就够了,分析的非常详细(代码片段)

类加载机制的原理1.启动JVM2.将需要运行的class文件加载到虚拟机内存中3.找到主类,开始执行主函数加载步骤:1.先委托父类加载类,如果父类已经加载,就不需要再次加载,如果父类没有加载,再由本加载器加载2.解析类路径,... 查看详情

jvm之类加载机制(代码片段)

...程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以... 查看详情

28java类的加载机制什么是类的加载类的生命周期加载:查找并加载类的二进制数据连接初始化类加载器双亲委派模型自定义类加载器(代码片段)

28Java类的加载机制28.1.什么是类的加载28.2.类的生命周期28.2.1.加载:查找并加载类的二进制数据28.2.2.连接28.2.3.初始化28.3.类加载器28.4.类的加载28.5.双亲委派模型28.6.自定义类加载器28.Java类的加载机制28.1.什么是类的加载类的... 查看详情

深入理解jvm虚拟机读书笔记——类的加载机制(代码片段)

...书可以关注WX公众号,回复001获取。Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。1. 查看详情

深入理解jvm虚拟机读书笔记——类的加载机制(代码片段)

...书可以关注WX公众号,回复001获取。Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。1. 查看详情

jvm类加载机制(代码片段)

...需要将编译好的class文件加载到JVM运行时数据区。在了解类的加载机制之前,我们需要了解一下类的生命周期。Java类从被加载到JVM内存开始,到卸载出内存为止,它的整个生命周期包括了:加载(Loading),验证(Verification),... 查看详情

类加载机制(代码片段)

类的生命周期  一个java文件的整个生命周期,总共要经历加载-验证-准备-解析-初始化-使用-卸载这几个阶段,有的人把验证准备解析归纳为一个阶段称为链接,所有有的说5个阶段的,也有说7个阶段的,两种说法。什么时候开... 查看详情

jvm类加载机制详解(代码片段)

...程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以... 查看详情

虚拟机类加载机制(代码片段)

概述Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。类的生命周期一个类型从被加载到虚拟机内存中... 查看详情

类加载机制(代码片段)

类是在运行期间动态加载的。类的生命周期 包括以下7个阶段:加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)使用(Using)卸载(Unloading)其中解析过程在某些情况下可以在初始化阶... 查看详情

jvm活学活用——类加载机制(代码片段)

类的实例化过程 有父类的情况1.加载父类静态   1.1为静态属性分配存储空间并赋初始值   1.2执行静态初始化块和静态初始化语句(从上至下)2.加载子类静态   2.1为静态属性分配存储空间 ... 查看详情