JVM之类加载子系统

JVM之类加载子系统

一、类加载的时机

类加载主要有四个时机:

  1. 遇到 new 、 getstatic 、 putstatic 和 invokestatic 这四条指令时,如果对应的类没有初始
    化,则要对对应的类先进行初始化。
public class Student{
	private static int age ;
	public static void method(){
	}
}
//Student.age
//Student.method();
//new Student();
  1. 使用 java.lang.reflect 包方法时,对类进行反射调用的时候。
Class c = Class.forname("com.hero.Student");
  1. 初始化一个类的时候发现其父类还没初始化,要先初始化其父类
  2. 当虚拟机开始启动时,用户需要指定一个主类(main),虚拟机会先执行这个主类的初始化。

二、类加载的过程

类加载主要做三件事:

1、类的全限定名称 ===> 二进制字节流加载class文件

2、字节流的静态数据结构 ===> 方法区的运行时数据结构

3、创建字节码Class对象

一个类的一生:

在这里插入图片描述

  • 加载:通过全类名获取定义此类的二进制字节流,将字节流所代表的静态存储结构转换为方法区的运行时数据结构,在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口。

  • 验证:确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

  • 准备准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,在 JDK 7 及之后,HotSpot 已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这个时候类变量则会随着 Class 对象一起存放在 Java 堆中。

  • 解析解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

  • 初始化初始化阶段是执行初始化方法 ()方法的过程,是类加载的最后一步,这一步 JVM 才开始真正执行类中定义的 Java 程序代码(字节码)。

  • 使用

  • 卸载:**卸载类即该类的 Class 对象被 GC,**要求:该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象,该类在没有在其他任何地方被引用,该类的类加载器的实例已被GC。

三、类加载器

JVM的类加载是通过ClassLoader及其子类来完成的。

在这里插入图片描述

检查顺序是自底向上:加载过程中会先检查类是否被已加载,从Custom ClassLoader到BootStrap
ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。
加载的顺序是自顶向下:也就是由上层来逐层尝试加载此类。

在这里插入图片描述

  • 启动类加载器(Bootstrap ClassLoader):
    负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟
    机认可(按文件名识别,如rt.jar)的类。由C++实现,不是ClassLoader的子类
  • 扩展类加载器(Extension ClassLoader):
    负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
  • 应用程序类加载器(Application ClassLoader):
    负责加载用户路径classpath上的类库
  • 自定义类加载器(User ClassLoader):
    作用:JVM自带的三个加载器只能加载指定路径下的类字节码,如果某个情况下,我们需要加
    载应用程序之外的类文件呢?就需要用到自定义类加载器,就像是在汽车行驶的时候,为汽车
    更换轮子。
    比如本地D盘下的,或者去加载网络上的某个类文件,这种情况就可以使用自定义加载器了。
    举个栗子:JRebel

自定义类加载器案例:
目标:自定义类加载器,加载指定路径在D盘下的lib文件夹下的类。
步骤:

  1. 新建一个需要被加载的类Test.jave
  2. 编译Test.jave到指定lib目录
  3. 自定义类加载器DreamClassLoader继承ClassLoader:
    重写findClass()方法
    调用defineClass()方法
  4. 测试自定义类加载器

实现

(1)新建一个 Test.java 类,代码如下:

package com.dreamoy.jvm.classLoader;

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

(2)使用 javac Test.java 命令,将生成的 Test.class 文件放到D:/lib/com/dreamoy/jvm/classloader 文件夹下。

(3)自定义类加载器,代码如下:

package com.dreamoy.jvm.classLoader;

import java.io.*;

public class DreamClassLoader extends ClassLoader {

    private String classpath;

    public DreamClassLoader(String classpath) {
        this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            //输入流,通过类的全限定名称加载文件到字节数组
            byte[] classData = getData(name);
            if (classData != null) {
                //defineClass方法将字节数组数据 转为 字节码对象
                return defineClass(name, classData, 0, classData.length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    //加载类的字节码数据
    private byte[] getData(String className) throws IOException {
        String path = classpath + File.separatorChar +
                className.replace('.', File.separatorChar) + ".class";
        try (InputStream in = new FileInputStream(path);
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[2048];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            return out.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

(4)测试,代码如下:

package com.dreamoy.jvm.classLoader;

import java.lang.reflect.Method;

public class TestMyClassLoader {
    public static void main(String []args) throws Exception{
        //自定义类加载器的加载路径
        DreamClassLoader dClassLoader=new DreamClassLoader("D:\\lib");
        //包名+类名
        Class c = dClassLoader.findClass("com.dreamoy.jvm.classloader.Test");
        if(c!=null){
            Object obj=c.newInstance();
            Method method=c.getMethod("say", null);
            method.invoke(obj, null);
            System.out.println(c.getClassLoader().toString());
        }
    }
}

输出结果如下:

在这里插入图片描述

四、双亲委派模型与打破双亲委派

什么是双亲委派?
当一个类加载器收到类加载任务,会先交给其父类加载器去完成。因此,最终加载任务都会传递到顶层
的启动类加载器,只有当父类加载器无法完成加载任务时,子类才会尝试执行加载任务。

相关源码:

    /**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @param  resolve
     *         If <tt>true</tt> then resolve the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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;
        }
    }

为什么需要双亲委派呢?

  • 避免原始类被覆盖的问题
  • 比如:
    • 用户编写了一个java.lang.Object类放入程序中加载,系统中位于rt.jar包中的也有同样的类
      java.lang.Object。这不就重复了嘛!
    • 有了双亲委派,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加
      载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。

为什么还需要破坏双亲委派?

  • 在某些情况下,父类加载器需要加载的class文件受到加载范围的限制,无法加载到需要的文件,这
    个时候就需要委托子类加载器进行加载。这种情况就打破了双亲委派模式。

  • 与双亲委派冲突:

    • 热加载技术
    • MySQL驱动
    • Tomcat加载多个应用程序
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值