您的当前位置:首页正文

JVM系列(4)

2024-11-27 来源:个人技术集锦



运行结果是2,因为Son类是Father类的子类,在执行Son类的加载前会先执行Father类的加载。



**虚拟机必须保证一个类的`<clinit>()`方法在多线程下被同步加锁:**



public class DeadThreadTest {

public static void main(String[] args) {

    Runnable r = () -> {

        System.out.println(Thread.currentThread().getName() + "开始");

        DeadThread dead = new DeadThread();

        System.out.println(Thread.currentThread().getName() + "结束");

    };



    Thread t1 = new Thread(r, "线程1");

    Thread t2 = new Thread(r, "线程2");



    t1.start();

    t2.start();

}

}

class DeadThread {

static {

    if (true) {

        System.out.println(Thread.currentThread().getName() + "初始化当前类");

        while (true) {



        }

    }

}

}




**结果分析:**



*   两个线程同时去初始化 DeadThread 类,而 DeadThread 类中静态代码块中有一处死循环,切该类只能被初始化一次

*   所以先初始化 DeadThread 类的线程抢到了同步锁,然后在类的静态代码块中执行死循环,而另一个线程在等待同步锁的释放

*   所以运行结果是:某一个线程先执行 DeadThread 类的初始化,而另外一个类不会继续执行  

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/2021060308591665.png)



[](https://gitee.com/vip204888/java-p7)类加载器的分类

--------------------------------------------------------------------



JVM支持两种类型的类加载器 ,分别为**引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。**



*   从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是**将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器,所以ExtClassLoader 和 AppClassLoader 都属于自定义加载器。**



**四者之间是包含关系,不是上层和下层,也不是子父类的继承关系:**  

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210602084917563.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0x6eTQxMDk5Mg==,size_16,color_FFFFFF,t_70)



**测试类加载器:**



public class ClassLoaderTest {

public static void main(String[] args) {



    //获取系统类加载器

    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

    System.out.println(systemClassLoader);

    //sun.misc.Launcher$AppClassLoader@18b4aac2



    //获取其上层:扩展类加载器

    ClassLoader extClassLoader = systemClassLoader.getParent();

    System.out.println(extClassLoader);

    //sun.misc.Launcher$ExtClassLoader@1540e19d



    //获取其上层:获取不到引导类加载器

    ClassLoader bootstrapClassLoader = extClassLoader.getParent();

    System.out.println(bootstrapClassLoader);

    //null



    //对于用户自定义类来说:默认使用系统类加载器进行加载

    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();

    System.out.println(classLoader);

    //sun.misc.Launcher$AppClassLoader@18b4aac2



    //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。

    ClassLoader classLoader1 = String.class.getClassLoader();

    System.out.println(classLoader1);

    //null



}

}




*   在获取引导类加载器时,获取到的值为 null ,因为引导类加载器由 C/C++ 语言编写,我们无法获取

*   两次获取系统类加载器的值都相同 ,说明系统类加载器全局唯一



**虚拟机自带的加载器:**



**启动类加载器(引导类加载器,Bootstrap ClassLoader)**



*   这个类加载使用**C/C++语言实现的**,嵌套在JVM内部

*   它用来加载Java的核心库(JAVA\_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类

*   并不继承自java.lang.ClassLoader,没有父加载器

*   加载扩展类和应用程序类加载器,并作为他们的父类加载器(当他俩的爹)

*   出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类



**扩展类加载器(Extension ClassLoader)**



*   Java语言编写,由sun.misc.Launcher$ExtClassLoader实现

*   派生于ClassLoader类

*   父类加载器为启动类加载器

*   从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载



**应用程序类加载器(系统类加载器,AppClassLoader)**



*   Java语言编写,由sun.misc.LaunchersAppClassLoader实现

*   派生于ClassLoader类

*   父类加载器为扩展类加载器

*   它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库

*   该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载

*   通过classLoader.getSystemclassLoader()方法可以获取到该类加载器



**代码举例说明:**



public class ClassLoaderTest1 {

public static void main(String[] args) {



    System.out.println("**********启动类加载器**************");

    //获取BootstrapClassLoader能够加载的api的路径

    URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();

    for (URL element : urLs) {

        System.out.println(element.toExternalForm());

    }

    //从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器

    ClassLoader classLoader = Provider.class.getClassLoader();

    System.out.println(classLoader);//null,证明我们无法获取到启动类加载器



    System.out.println("***********扩展类加载器*************");

    String extDirs = System.getProperty("java.ext.dirs");

    for (String path : extDirs.split(";")) {

        System.out.println(path);

    }



    //从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器

    ClassLoader classLoader1 = CurveDB.class.getClassLoader();

    System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d



}

}




运行结果:  

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210603091246609.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0x6eTQxMDk5Mg==,size_16,color_FFFFFF,t_70)



**用户自定义类加载器:**



在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。那为什么还需要自定义类加载器?



*   隔离加载类

*   修改类加载的方式

*   扩展加载源

*   防止源码泄漏



**如何自定义类加载器?**



*   开发人员可以通过**继承抽象类java.lang.ClassLoader类**的方式,实现自己的类加载器,以满足一些特殊的需求

*   在JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadClass()方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议**把自定义的类加载逻辑写在findclass()方法中**

*   在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findclass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。



**代码示例:**



public class CustomClassLoader extends ClassLoader {

@Override

protected Class<?> findClass(String name) throws ClassNotFoundException {



    try {

        byte[] result = getClassFromCustomPath(name);

        if (result == null) {

            throw new FileNotFoundException();

        } else {

            return defineClass(name, result, 0, result.length);

        }

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    }



    throw new ClassNotFoundException(name);

}



private byte[] getClassFromCustomPath(String name) {

    //从自定义路径中加载指定类:细节略

    //如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。

    return null;

}



public static void main(String[] args) {

    CustomClassLoader customClassLoader = new CustomClassLoader();

    try {

        Class<?> clazz = Class.forName("One", true, customClassLoader);

        Object obj = clazz.newInstance();

        System.out.println(obj.getClass().getClassLoader());

    } catch (Exception e) {

        e.printStackTrace();

    }

}

}




**ClassLoader 类:**



ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)  

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210603091609518.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0x6eTQxMDk5Mg==,size_16,color_FFFFFF,t_70)



获取 ClassLoader 途径:  

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210603091705396.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0x6eTQxMDk5Mg==,size_16,color_FFFFFF,t_70)



**代码示例:**



public class ClassLoaderTest2 {

public static void main(String[] args) {

    try {

        

        //1.Class.forName().getClassLoader()

        ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();

        System.out.println(classLoader); // String 类由启动类加载器加载,我们无法获取



        //2.Thread.currentThread().getContextClassLoader()

        ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();

        System.out.println(classLoader1);



        //3.ClassLoader.getSystemClassLoader().getParent()

        ClassLoader classLoader2 = ClassLoader.getSystemClassLoader();

        System.out.println(classLoader2);



    } catch (ClassNotFoundException e) {

        e.printStackTrace();

    }

}

}




运行结果:  

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210603091743165.png)



[](https://gitee.com/vip204888/java-p7)双亲委派机制

-------------------------------------------------------------------



**双亲委派机制的原理:**



Java虚拟机对class文件采用的是**按需加载**的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是**双亲委派模式**,即把请求交由父类处理,它是一种任务委派模式。



*   如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;

*   如果父类加载器还存在其父类加载器,则进一步**向上委托,依次递归,请求最终将到达顶层的启动类加载器**;

*   如果父类加载器可以完成类加载任务,就成功返回,倘**若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载**,这就是双亲委派模式。

*   父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常



![在这里插入图片描述](https://img-blog.csdnimg.cn/20210603093423408.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0x6eTQxMDk5Mg==,size_16,color_FFFFFF,t_70)



**示例1:创建一个 java.lang.String 类,写上 static 代码块并使用**



package java.lang;

public class String {

static{

    System.out.println("我是自定义的String类的静态代码块");

}

}




在另外的程序中加载 String 类,看看加载的 String 类是 JDK 自带的 String 类,还是我们自己编写的 String 类



public class StringTest {

public static void main(String[] args) {

    java.lang.String str = new java.lang.String();

    System.out.println("hello");



    StringTest test = new StringTest();

    System.out.println(test.getClass().getClassLoader());

}

}




程序并没有输出我们静态代码块中的内容,可见仍然加载的是 JDK 自带的 String 类:  

![在这里插入图片描述](https://img-blog.csdnimg.cn/2021060309303079.png)



**举例 2:在我们自己的 String 类中添加 main() 方法**



package java.lang;

public class String {

static{

    System.out.println("我是自定义的String类的静态代码块");

}

//错误: 在类 java.lang.String 中找不到 main 方法

public static void main(String[] args) {

    System.out.println("hello,String");

}

}




由于双亲委派机制找到的是 JDK 自带的 String 类,在那个 String 类中并没有 main() 方法  

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210603093111533.png)



**举例 3:在 java.lang 包下新建 ShkStart 类**



package java.lang;

public class ShkStart {

public static void main(String[] args) {

    System.out.println("hello!");

}

}




出于保护机制,java.lang 包下不允许我们自定义类  

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210603093202635.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0x6eTQxMDk5Mg==,size_16,color_FFFFFF,t_70)



**举例 4 :当我们加载jdbc.jar 用于实现数据库连接的时候**



*   jdbc.jar是基于SPI接口进行实现的,在加载的时候,会进行双亲委派,最终从根加载器中加载 SPI核心类,然后再加载SPI接口类

*   接着在进行反向委托,通过线程上下文类加载器进行实现类 jdbc.jar的加载。  

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210603093644548.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0x6eTQxMDk5Mg==,size_16,color_FFFFFF,t_70)



**总结:通过上面的例子,我们可以知道**



*   **双亲机制避免了类的重复加载**

*   **保护程序安全,防止核心API被随意篡改**  

    自定义类:java.lang.String 没有调用  

    自定义类:java.lang.ShkStart(报错:阻止创建 java.lang开头的类)



[](https://gitee.com/vip204888/java-p7)沙箱安全机制

-------------------------------------------------------------------



*   自定义String类时:在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java.lang.String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类。

*   这样可以保证对java核心源代码的保护,这就是沙箱安全机制。



[](https://gitee.com/vip204888/java-p7)其他

---------------------------------------------------------------


*   自定义String类时:在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java.lang.String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类。

*   这样可以保证对java核心源代码的保护,这就是沙箱安全机制。


[](https://gitee.com/vip204888/java-p7)其他

---------------------------------------------------------------





显示全文