自学JVM一、从JDK源码剖析JVM类加载机制

一、java命令执行代码大致流程

 loadClass类加载过程有如下几步:

        加载 => 验证 => 准备 => 解析 => 初始化 => 使用 => 卸载

                加载:在硬盘上查找并通过IO读取字节码文件。使用到的类才会被加载,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为这个类在各种数据的访问入口(方法区的入口)

                验证:校验字节码文件的正确性

                准备:给类的静态变量分配内存,并赋予默认值

                解析:将符号引用替换为直接引用。该阶段会将一些静态方法替换为指向数据实际存储内存的指针或句柄(即将符号引用替换为直接引用)。这就是静态链接的过程(在加载期间完成的)。而动态链接是在程序运行期间完成的将符号引用替换为直接引用的过程。

                初始化:对类的静态变量初始化为指定的值,执行静态代码块。

                

        类被加载到方法区后,主要包含:运行时常量池类型信息字段信息方法信息类加载器的引用对应class实例的引用等信息。

        1)类加载器的引用:这个类到类加载器实例的引用

        2)对应class实例的引用:类加载器在加载类信息放到方法区后,他创建一个对应的class类型的对象实例放到堆中,作为程序访问方法区中类定义的入口和切入点。

        注意:JVM加载类的过程是按需加载,如果主类在运行过程中要使用其他的类,会在使用时逐步加载这些类。

public class ClassLoaderTest {

    @Test
    public void loadClass01(){
        new A();
        System.out.println("*************load test************");
        B b = null;
    }
}

class A{
    static{
        System.out.println("this is class A");
    }

    public A() {
        System.out.println("initialize A");
    }
}
class B{
    static{
        System.out.println("this is class B");
    }

    public B() {
        System.out.println("initialize B");
    }
}


+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
执行结果如下:
this is class A
initialize A
*************load test************

虽然创建了B的变量,但是并没有实例化,只会创建一个类放到内存中。

二、类加载器和双亲委派机制

类加载的过程主要通过类加载器实现,JAVA中的类加载器

        1)引导类加载器(bootstrapClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库。如dt.jar

        2)扩展类加载器(ExtClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的Jar包

        3)应用程序类加载器(AppClassLoader):负责加载ClassPath路径下的类包,主要是加载项目中业务和技术代码。

        4)自定义类加载器:负载加载用户指定的路径下的类包

public class ClassLoaderTest {

    @Test
    public void classLoader01(){
        System.out.println("String:" + String.class.getClassLoader());
        System.out.println("DESKeyFactory:" + DESKeyFactory.class.getClassLoader());
        System.out.println("ClassLoaderTest:" + ClassLoaderTest.class.getClassLoader());
        System.out.println("************************************************************************");

        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassLoader = appClassLoader.getParent();
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println("bootstrapLoader:" + bootstrapClassLoader);
        System.out.println("extClassLoader" + extClassLoader);
        System.out.println("appClassLoader:" + appClassLoader);
        System.out.println("************************************************************************");

        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for(URL url : urLs){
            System.out.println(url);
        }
        System.out.println("************************************************************************");

        System.out.println("extClassLoader load file:" + System.getProperty("java.ext.dirs"));
        System.out.println("************************************************************************");

        System.out.println("appClassLoader load file:" + System.getProperty("java.class.path"));
    }
}



+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
执行结果:
String:null
DESKeyFactory:sun.misc.Launcher$ExtClassLoader@6d03e736
ClassLoaderTest:sun.misc.Launcher$AppClassLoader@18b4aac2
************************************************************************
bootstrapLoader:null
extClassLoadersun.misc.Launcher$ExtClassLoader@6d03e736
appClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
************************************************************************
file:/D:/a_install/jdk1.8.0_162/jre/lib/resources.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/rt.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/sunrsasign.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/jsse.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/jce.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/charsets.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/jfr.jar
file:/D:/a_install/jdk1.8.0_162/jre/classes
************************************************************************
extClassLoader load file:D:\a_install\jdk1.8.0_162\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
************************************************************************
appClassLoader load file:D:\a_install\idea\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar;D:\a_install\idea\IntelliJ IDEA 2019.3.3\plugins\junit\lib\junit5-rt.jar;D:\a_install\idea\IntelliJ IDEA 2019.3.3\plugins\junit\lib\junit-rt.jar;D:\a_install\jdk1.8.0_162\jre\lib\charsets.jar;D:\a_install\jdk1.8.0_162\jre\lib\deploy.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\access-bridge-64.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\cldrdata.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\dnsns.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\jaccess.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\jfxrt.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\localedata.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\nashorn.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\sunec.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\sunjce_provider.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\sunmscapi.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\sunpkcs11.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\zipfs.jar;D:\a_install\jdk1.8.0_162\jre\lib\javaws.jar;D:\a_install\jdk1.8.0_162\jre\lib\jce.jar;D:\a_install\jdk1.8.0_162\jre\lib\jfr.jar;D:\a_install\jdk1.8.0_162\jre\lib\jfxswt.jar;D:\a_install\jdk1.8.0_162\jre\lib\jsse.jar;D:\a_install\jdk1.8.0_162\jre\lib\management-agent.jar;D:\a_install\jdk1.8.0_162\jre\lib\plugin.jar;D:\a_install\jdk1.8.0_162\jre\lib\resources.jar;D:\a_install\jdk1.8.0_162\jre\lib\rt.jar;D:\projects\extraTools\FileProcessor\target\test-classes;D:\projects\extraTools\FileProcessor\target\classes;D:\a_install\maven\repository\com\alibaba\fastjson\1.2.47\fastjson-1.2.47.jar;D:\a_install\maven\repository\org\apache\poi\poi\4.0.1\poi-4.0.1.jar;D:\a_install\maven\repository\commons-codec\commons-codec\1.11\commons-codec-1.11.jar;D:\a_install\maven\repository\org\apache\commons\commons-collections4\4.2\commons-collections4-4.2.jar;D:\a_install\maven\repository\org\apache\commons\commons-math3\3.6.1\commons-math3-3.6.1.jar;D:\a_install\maven\repository\org\apache\poi\poi-ooxml\4.0.1\poi-ooxml-4.0.1.jar;D:\a_install\maven\repository\org\apache\poi\poi-ooxml-schemas\4.0.1\poi-ooxml-schemas-4.0.1.jar;D:\a_install\maven\repository\org\apache\xmlbeans\xmlbeans\3.0.2\xmlbeans-3.0.2.jar;D:\a_install\maven\repository\org\apache\commons\commons-compress\1.18\commons-compress-1.18.jar;D:\a_install\maven\repository\com\github\virtuald\curvesapi\1.05\curvesapi-1.05.jar;D:\a_install\maven\repository\dom4j\dom4j\1.6.1\dom4j-1.6.1.jar;D:\a_install\maven\repository\xml-apis\xml-apis\1.0.b2\xml-apis-1.0.b2.jar;D:\a_install\maven\repository\junit\junit\4.12\junit-4.12.jar;D:\a_install\maven\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\a_install\maven\repository\com\ibm\db2\jcc\db2-spring-framework\0.0.1\db2-spring-framework-0.0.1.jar;D:\a_install\maven\repository\com\ibm\db2\jcc\11.5.0.0\jcc-11.5.0.0.jar;D:\a_install\maven\repository\commons-io\commons-io\2.5\commons-io-2.5.jar;D:\a_install\maven\repository\org\freemarker\freemarker\2.3.31\freemarker-2.3.31.jar;D:\a_install\maven\repository\com\ibm\db2\jcc\db2jcc\db2jcc4\db2jcc-db2jcc4.jar;D:\a_install\maven\repository\org\springframework\spring-webmvc\5.2.11.RELEASE\spring-webmvc-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-aop\5.2.11.RELEASE\spring-aop-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-beans\5.2.11.RELEASE\spring-beans-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-context\5.2.11.RELEASE\spring-context-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-core\5.2.11.RELEASE\spring-core-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-jcl\5.2.11.RELEASE\spring-jcl-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-expression\5.2.11.RELEASE\spring-expression-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-web\5.2.11.RELEASE\spring-web-5.2.11.RELEASE.jar;D:\a_install\idea\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar

关注点及说明

        1)bootstrapClassLoader是C++的类加载器,java中为null

        2)bootstrapClassLoader加载的核心类库

        3)extClassLoader加载的ext扩展目录的类库

        4)appClassLoader加载的classpath目录下的jar包

类加载器初始化过程

        C++创建JVM启动器实例sun.misc.Launcher。sun.misc.Launcher初始化使用了单例模式,保证虚拟机只有sun.misc.Launcher实例。

        Launcher类的构造方法内部创建了两个类加载器:ExtClassLoader和AppClassLoader,JVM默认使用Launcher类的getClassLoader()方法返回的类加载器(类型为应用类加载器)的实例来加载应用程序的类

双亲委派机制

 原理:当加载一个类时,当前类加载器(可能是应用类加载器,也可能是自定义类加载器)会先在自己已加载的类中查询是否已经加载该类,如果未加载,则委托父类加载器尝试加载这个类,父类加载器同逻辑处理,直至引导类加载器(bootstrapClassLoader),如果引导类加载器在已加载类中未查询到该类,则到引导类加载路径尝试加载类,然后返回加载结果给扩展类加载器,扩展类加载器判断类已被加载,则直接返回;否则扩展类加载器会到自己的加载路径尝试加载类,然后返回加载结果给应用类加载器,应用类加载器判断类已被加载就返回,如果未被加载就到应用类加载路径尝试加载类,如果加载成功就把该类,如果加载失败就报ClassNotFoundException(如果有自定义类加载器,应用类加载器会)。

双亲委派机制优点

        1)水箱安全机制:防止核心API类库被随意篡改。比如:java.lang.String类手写版也不会被加载,只会加载核心类库中的java.lang.String类

        2)避免类的重复加载:保证被加载的类的唯一性。一个类被加载一次后,就不会再进行重复加载。

全盘负责委托机制
        全盘负责:当一个ClassLoader装载一个类时,除非显式的使用另一个ClassLoader,否则该类及该类所依赖的类和该类引用的类都由这个ClassLoader装载。

自定义类加载器

        自定义类加载器只需要继承java.lang.ClassLoader类,该类有两个核心方法,loadClass(String,boolean),用来实现双亲委派机制;findClass(String),默认为空方法,需要自定义类加载器重写findClass(String)方法。

package com.jvm.tt;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;

public class MyClassLoader extends ClassLoader {

    private String classPath;

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] bytes = loadByte(name);
            //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            throw new ClassNotFoundException();
        }
    }

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


    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("D:\\extra\\keepLearning\\TT\\SpringMVC\\JVMTT\\target\\classes");
        Class<?> clazz = myClassLoader.loadClass("com.jvm.tt.Study");
        Object obj = clazz.newInstance();
        Method main = clazz.getMethod("doStudy");
        main.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

class Study {

    public Study() {
    }

    public void doStudy(){
        System.out.println("studying");
    }
}


+++++++++++++++++++++++++++
执行结果:
studying
sun.misc.Launcher$AppClassLoader

打破双亲委派机制

package com.jvm.tt;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;

public class NoParentClassLoader extends ClassLoader{

    private String classPath;

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

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] bytes = loadByte(name);
            //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    /**
     *  重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
     * @param name
     * @param resolve
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)){
            // First, check if the class has already been loaded
            Class<?> loadedClass = findLoadedClass(name);
            if(loadedClass == null){
                // If still not found, then invoke findClass in order to find the class
                loadedClass = findClass(name);
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(System.nanoTime());
                sun.misc.PerfCounter.getFindClasses().increment();
            }
            if(resolve){
                resolveClass(loadedClass);
            }
            return loadedClass;
        }
    }

    public static void main(String[] args) throws Exception {
        NoParentClassLoader classLoader = new NoParentClassLoader("D:\\extra\\keepLearning\\TT\\SpringMVC\\JVMTT\\target\\classes");
        //尝试用自己改写类加载机制去加载自己写的java.lang.String.class
        Class<?> clazz = classLoader.loadClass("java.lang.Integer");
//        Object obj = ;
        Method method = clazz.getMethod("sout");
        System.out.println(method.invoke(clazz.newInstance()));
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}



++++++++++++++++++++++++++++++++++++
输出结果:
java.lang.SecurityException: Prohibited package name: java.lang
	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.jvm.tt.NoParentClassLoader.findClass(NoParentClassLoader.java:30)
	at com.jvm.tt.NoParentClassLoader.loadClass(NoParentClassLoader.java:51)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at com.jvm.tt.NoParentClassLoader.main(NoParentClassLoader.java:66)
Exception in thread "main" java.lang.ClassNotFoundException
	at com.jvm.tt.NoParentClassLoader.findClass(NoParentClassLoader.java:33)
	at com.jvm.tt.NoParentClassLoader.loadClass(NoParentClassLoader.java:51)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at com.jvm.tt.NoParentClassLoader.main(NoParentClassLoader.java:66)

在调用自定义的findClass时,defineClass方法会报Prohibited package name: java.lang错误,然后throw ClassNotFoundException才打印的下面ClassNotFoundException的错。

Tomcat打破双亲委派机制:

原因:

    1、Tomcat作为一个web容器,当需要部署两套不同的应用,且这两套应用会依赖同一个第三方库的不同版本,所以要求每个应用程序的类库都是相对独立的。

    2、部署在同一个web容器中的相同类库的相同版本应该可以共享。

    3、web容器有自己的类库,且不与应用程序混淆。

    4、web容器要支持jsp文件的热加载,即应用程序运行后,可以随时修改jsp文件,且可以即时生效,不需要重启。

Tomcat自定义类加载器详解

tomcat的几个主要类加载器:

        commonClassLoader: tomcat最基本的类加载器,加载路径中的class可以被tomcat容器本身以及各个webapp访问

        catalinaClassLoader:tomcat容器私有的类加载器,加载路径中的class只对tomcat容器可见,对webapp不可见

        sharedClassLoader:各个webapp共享的类加载器,加载路径中的class对于所有的webapp可见,但是对于tomcat不可见

        WebappClassLoader:各个webapp私有的类加载器,加载路径中的class只对当前webapp可见。如加载war包中的类,每个war包有自己的WebappClassLoader,实现相互隔离。

如图所示:

        CommonClassLoader能加载的类,可以被CatalinaClassLoader和SharedClassLoader使用,是共有类库。而CatalinaClassLoader和SharedClassLoader能加载的类则是相互隔离的。

        WebappClassLoader可以使用SharedClasLoader加载到的类,但各个WebappClassLoader加载的类库之间是相互隔离的。

        JasperClassLoader的加载范围仅是这个JSP文件所编译出来的那个.class文件。当web容器检测到JSP文件被修改,会替换掉当前的JasperClassLoader实例,然后新建一个JSP类加载器来实现JSP文件的热加载功能。

        每个WebappClassLoader加载自己目录下的class文件,且不会传递给父类加载器,打破了双亲委派机制。

模拟tomcat的WebappClassLoader加载自己war包应用,实现应用包内不同版本的类库相互共存与隔离

package com.jvm.tt;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;

public class BreakParentClassLoader extends ClassLoader {
    private String classPath;

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

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] bytes = loadByte(name);
            //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    /**
     *  重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
     * @param name
     * @param resolve
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)){
            // First, check if the class has already been loaded
            Class<?> loadedClass = findLoadedClass(name);
            if(loadedClass == null){
                // If still not found, then invoke findClass in order to find the class
                long nanoTime = System.nanoTime();
                //if not self-define class, call parent class loader, else load class by self-define class loader
                if(!name.startsWith("com.jvm.tt")){
                    loadedClass = getParent().loadClass(name);
                }else{
                    loadedClass = findClass(name);
                }
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(System.nanoTime());
                sun.misc.PerfCounter.getFindClasses().increment();
            }
            if(resolve){
                resolveClass(loadedClass);
            }
            return loadedClass;
        }
    }

    public static void main(String[] args) throws Exception {
        BreakParentClassLoader classLoader = new BreakParentClassLoader("D:\\extra\\keepLearning\\TT\\SpringMVC\\JVMTT\\target\\classes");
        Class<?> clazz = classLoader.loadClass("com.jvm.tt.entity.Student");
        Object obj = clazz.newInstance();
        Method method = clazz.getMethod("studying");
        method.invoke(obj);
        System.out.println(clazz.getClassLoader());

        System.out.println("*******************************************");

        BreakParentClassLoader selfClassLoader = new BreakParentClassLoader("D:\\extra\\keepLearning\\TT\\SpringMVC\\JVMTT\\target\\classes");
        Class<?> selfClass = selfClassLoader.loadClass("com.jvm.tt.entity.Student");
        Object selfObj = selfClass.newInstance();
        Method selfMethod = selfClass.getMethod("studying");
        selfMethod.invoke(selfObj);
        System.out.println(selfClass.getClassLoader());

    }
}


+++++++++++++++++++++++++++++++++++++++++++++++
运行结果:

=======自己的加载器加载类调用方法=======
com.jvm.tt.BreakParentClassLoader@14ae5a5
*******************************************
=======自己的加载器加载类调用方法=======
com.jvm.tt.BreakParentClassLoader@135fbaa4


注意:同一个JVM中,两个相同路径、相同类名的类对象可以共存,它们的类加载器不同,所以它们不是同一个类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值