性能调优(一)JVM的类加载机制

JVM的类加载机制所使用的JDK版本是JDK8

一、jvm的类加载机制过程

1.类加载机制的概述

用C++语言的启动程序启动java虚拟机,并创建了一个由C++语言实现的引导类加载器,接下来会使用C++调用真正java虚拟机里面启动程序,比如调用sun.misc.Launcher类,通过这个类创建很多java实现的类加载器,再通过这些类加载器最终调用loadClass方法去加载磁盘上的字节码文件;加载到内存之后,C++会直接调用类的main方法,调用main方法之后程序开始运行,直到程序结束运行,jvm销毁。

image

 

2.类加载机制的详细过程

步骤一:在命令行中输入"java xxx.class",java.exe调用底层jvm.dll文件创建java虚拟机,在创建java虚拟机的同时创建一个引导类加载器实例。
 
步骤二:引导类加载器加载Launcher类并初始化Launcher类创建实例对象,在初始化Launcher类的时候其内部创建扩展类加载器和应用程序类加载器。
具体过程:Launcher类有一个Launcher类型的launcher静态变量,并赋值new Launcher()。首先引导类加载器加载Launcher类,在加载过程中jvm给静态变量launcher赋初始值null。在加载完成后引导类加载器调用Launcher类的getLauncher()方法,返回静态变量launcher的值也就是Launcher类的实例化对象。而在此过程中变量launcher被访问,初始化静态变量的值,调用Launcher类的构造函数。在构造函数中调用Launcher.ExtClassLoader.getExtClassLoader();方法创建扩展类加载器,调用Launcher.AppClassLoader.getAppClassLoader(var1);创建应用程序类加载器。此时launcher实例化对象、扩展类加载器和应用程序类加载器完成创建。

image

 

步骤三:在初始化Launcher类过程中,将创建好的应用程序类加载器赋值给自己实例中变量loader。之后调用launcher.getClassLoader方法获取自己的加载器AppClassLoader实例。

image

 

步骤四:以Main函数为程序入口,jvm会检测要运行的代码中要用到的类是否已经加载,而这个加载类的过程是按照双亲委派机制来加载的。通过调用AppClassLoader.loadClass(String name,Boolean reslove)方法,最终是由该类的父类ClassLoader.loadClass方法来实现的。

image

 

步骤五:ClassLoader.loadClass方法的具体步骤为:AppClassLoader调用findLoadedClass【ClassLoader类中的方法】方法来看自己已经加载的类的集合中有没有这个类,如果有这个类直接返回,如果没有这个类,就让它的父加载器调用loadClass方法【ClassLoader类中的方法】去查找父加载器中已经加载的类的集合中有没有这个类,如果有,就直接返回,如果没有就调用findBootstrapClassOrNull()方法让启动类加载器去它负责的目录下寻找这个类并加载,如果找到这个类就加载并返回,如果没有找到,ExtClassLoader就调用findClass()方法【最终扩展类加载器还是应用程序类加载器最终都是调用的URLClassLoader类中的方法】,而这个方法就是去它负责的目录下查找这个二进制文件并加载到内存中。如果ExtClassLoader的findClass()方法找到这个类就返回,如果没有找到就让AppClassLoader调用findClass()方法去它负责的目录下查找并加载。

image

注意:JVM采用懒加载机制,程序启动时仅加载包含main方法的主类及其直接依赖的类,其余类(静态内部类、工具类等)只有在首次被主动使用时才会加载和初始化。

 

二、双亲委派机制

1.双亲委派机制原理

 

以Math类加载过程为例:

① 应用程序类加载器加载Math类时,先检查它已经加载过的类里面有没有Math类,如果有就直接返回;如果没有,它不会自己去加载,而是委派扩展类加载器去加载。

② 同样扩展类加载器也是先检查它已经加载过的类里面有没有Math类,如果有就直接返回;如果没有,它也不会自己去加载,而是委托引导类加载器去加载。

③ 引导类加载器先检查它已经加载的类里面有没有Math类,如果有直接返回;如果没有,它会尝试去它对应的加载路径中加载,如果找不到就不会加载,那么它就委托扩展类加载器去加载。

④ 扩展类加载器也去尝试加载,如果它也在它对应的加载路径中也找不到该类就不加载,那么它就委托应用程序类加载器去加载。

⑤应用程序类加载器就去它对应的加载路径中加载,最终加载成功。

注意:

类加载过程主要是通过类加载器来实现的,而java里有几种类加载器:

引导类加载器:负责加载支撑JVM运行的位于jdk/jre/lib目录下的核心类库

扩展类加载器:负责加载支撑JVM运行的位于jdk/jre/lib/ext扩展目录中的JAR 类包

应用程序类加载器:负责加载自己写的类(负责加载的目录位于ClassPath路径下的类包,即自己的项目中的类)

自定义加载器:负责加载用户自定义路径的类包

 

代码实现部分:

双亲委派机制的实现核心是由loadClass()方法来实现的。

接下来是需要确定几点关系:

应用程序类加载器(AppClassLoader)loadClass()的核心实现最终是调用ClassLoader.loadClass()方法来实现的。

首先是应用程序类加载器(AppClassLoader),它的继承关系如下:

\

在应用程序类加载器的loadClass()方法是调用它的父类URLClassLoader.loadClass()方法

 

而URLClassLoader.loadClass()方法又是调用它的父类loadClass()方法的,因为它的父类SecureClassLoader没有loadClass()方法,调用了ClassLoader.loadClass()方法,也就是说AppClassLoader的loadClass()方法最终是由ClassLoaDER.loadClass()方法来实现的

 

扩展类加载器(ExtClassLoader)没有loadClass()方法,因为它继承了它的父类的URLClassLoader.loadClass()方法,最终也是调用ClassLoader.loadClass()方法来实现的。

③ ClassLoader类中的Parent变量是扩展类加载器(ExtClassLoader)

 

代码实现:

 

2.loadClass方法在类加载过程

在类加载时,双亲委派机制通过类加载器在指定的加载路径中找到该类,并开始加载的具体过程如下:

而实现类加载过程主要是ClassLoader类的loadClass()方法进行类加载,而类中加载的核心是loadClass()方法中调用findClass方法来实现这一过程的。在类加载的过程有如下几步:

加载->>验证->>准备->>解析->>初始化

 

加载:在加载路径中找到指定的class文件,在磁盘中读取指定的class文件,并将class文件丢到java虚拟机内存中去

验证:验证字节码格式是否正确。比如:防止出现编译好的字节码被篡改

准备:将一些静态变量做一个默认值赋值。在类加载的过程中,静态变量是先赋默认值的。比如:代码中是public static int intData=23;那么在这一阶段就是给变量initData赋值为0,此时initData=0。同理String类型的变量赋值为null,boolean类型的变量赋值为false。

解析:java虚拟机将常量池内的符号引用替换为直接引用的过程。常量池内的符号引用指的是存储在磁盘上的.class文件在编译期生成的二进制资源表,跟内存没有关系,用来存储字面量和符号引用的,用于JVM加载时解析为直接引用。直接引用指的是直接指向目标在内存中的地址。可以理解为将存储在.class文件中的符号描述转换为在jvm内存中的内存地址。
初始化:对类的静态变量赋值为用户指定的值,同时这一阶段静态代码块也会执行的。比如:代码public static int age=20;在"准备"这一阶段将age赋为默认值0,即age=0,在初始化这一阶段将age=20。

3.为什么要双亲委派机制

a.沙箱安全机制:比如自己写了一个java.lang.String.class类不会被加载,这样可以防止核心API库被随意篡改

b.避免类的重复加载:当父加载器已经加载了该类时,就没有必要子加载器再加载一次,保证了被加载的类的唯一性。

 

三、自定义类加载器

自定义类加载器只需要继承java.lang.ClassLoader类,该类有两个核心方法,一个是loadClass(String,boolean)实现双亲委派机制,另一个方法是findClass方法,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。

public class MyClassLoader extends ClassLoader {

    private String classPath;

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

    //将磁盘上指定的字节码文件读取转换为二进制
    private byte[] getClassBytes(String className)  throws Exception{
        className = className.replaceAll("\\.","/");
        FileInputStream fs = new  FileInputStream(classPath+"/"+className+".class");
        int len=fs.available();
        byte[] data=new byte[len];
        fs.read(data);
        fs.close();
        return data;

    }

    //重写ClassLoader类中的findClass方法
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        try{
            byte[] data = getClassBytes(className);
            return defineClass(className,data,0,data.length);
        } catch(Exception e){
            e.printStackTrace();
            throw new ClassNotFoundException(className);
        }

    }

}



===================调用
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {
MyClassLoader myClassLoader = new MyClassLoader("D:/test");
Class clazz=myClassLoader.loadClass("com.blackboy.test2.User");
System.out.println(clazz.getClassLoader().getClass().getName());

}
注意:自定义类加载器的父亲类加载器默认是应用程序类加载器

 

四、打破双亲委派机制

打破双亲委派机制就是自定义类加载器自己加载,不走上面的路了

image

 

实现双亲委派机制的核心是ClassLoader类的loadClass方法,那么只需要修改findClass方法,那么可以将findClass方法复制到自定义类加载器中,由于我们写的类需要依赖核心类,jdk不允许核心类通过外部加载器去加载,因此我们可以根据包名来判断是否是自定义类加载器加载还是由原来的双亲委派机制来加载。
public class MyClassLoader extends ClassLoader {

    private String classPath;

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

    //将磁盘上指定的字节码文件读取转换为二进制
    private byte[] getClassBytes(String className)  throws Exception{
        className = className.replaceAll("\\.","/");
        FileInputStream fs = new  FileInputStream(classPath+"/"+className+".class");
        int len=fs.available();
        byte[] data=new byte[len];
        fs.read(data);
        fs.close();
        return data;

    }

    //重写ClassLoader类中的findClass方法
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        try{
            byte[] data = getClassBytes(className);
            return defineClass(className,data,0,data.length);
        } catch(Exception e){
            e.printStackTrace();
            throw new ClassNotFoundException(className);
        }

    }

    //重写父类的loadClass方法
    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();
                long t1 = System.nanoTime();


                //核心部分:如果不是自定义类加载器要加载的类,那么就有系统原来的双亲委派机制进行加载;
                //如果
                if (!name.startsWith("com.blackboy.test2")){
                    c=this.getParent().loadClass(name);
                }else{
                    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;
        }
    }



}

 

 

 

posted @ 2026-07-10 01:47  冯继强fjq  阅读(6)  评论(0)    收藏  举报