当前位置:网站首页>JVM类加载子系统

JVM类加载子系统

2022-07-06 09:30:00 机智的爆爆哥

1. 类加载器与类加载的过程

image.png

我们先来看张图,字节码文件经过类加载系统的过程,首先我们要明确,类加载器的作用是什么?

主要是起了一个传输的作用,相当于快递员,后面的过程可以看成是流水线,会对相应的文件进行验证,判断是否合法。

类的加载过程

image.png

加载

这里的加载只是整个加载的一个最初流程,这点需要注意,加载主要分为三步。

1.通过类的全限定类名获取对应类的二进制字节流。

2.将字节流代表的静态结构转化为方法区的运行时结构。

3.在内存中生成对应的java.lang.Class对象。

链接

链接细分为三步,这三步都很重要。

验证

可能有人恶意更改class文件,实现某种攻击,所以要先做验证。

准备

  • 为类变量赋值为零值
  • 加final的变量在编译期就被定义了,所以不会赋值
  • 这个时候还没有创建对象呢,所以实例变量也不会进行赋值

解析
将符号引用转化为直接引用,具体的细节字节码篇再讲。

初始化

首先要明白 clint方法和init方法,前者是由所有类变量的赋值,静态语句合并在一起的方法,后者是有几个构造器,就会有几个对应的init方法。

clint方法说白了就是用static修饰的语句,就会被加入到里面,如果一个类有父类,那么会先执行父类的clinit方法。

init方法里面包含了构造器,显式初始化,如直接赋值,代码块,实例化都算在里面。

而整个初始化阶段就是执行clinit方法的,并且在执行clinit方法会加锁,保证数据的安全性。

2. 类加载器分类

类加载器主要分为两类,一类是C语言编写的,被称为引导类加载器,其他都可算作自定义加载器,具体如下所示。

image.png

其中最上层是最特殊的,其他的都是由Java语言实现的。

可以运行如下代码,尝试获取下类加载器。

public class Test {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$ [email protected]

        //获取扩展类加载器 
        ClassLoader extClassLoafer = systemClassLoader.getParent();
        System.out.println(extClassLoafer);//sun.misc.Launcher$  [email protected]

        //用户自定义类加载器  
        ClassLoader classLoader = Test.class.getClassLoader();
	
        System.out.println(classLoader);//sun.misc.Launcher$  [email protected]

        //尝试获取引导类加载器  
        ClassLoader bootstrapClassloader = extClassLoafer.getParent();
        System.out.println(bootstrapClassloader);//null
    }
}

可以看到,我们发现,自定义类的加载器是系统类加载器,系统类加载器的上级是引导类加载器,引导类加载器的上级为null,因为是C语言写的,咱们获取不到。

3. ClassLoader的使用说明

Bootstrap classLoader

引导类加载器,也被称为启动类加载器,最上级的角色,用C语言编写,用来加载一些核心的类,如JAVA_HOME/jre/lib/rt.jar、resource.jar,提供JVM自身所需要的类。

Extension classLoader

扩展类加载器,派生于ClassLoader类,由Java语言编写,加载核心包以外的内容,如用户建的Jar包放在jre/lib/ext目录下,也会被扩展类加载器所加载。

AppClassLoader

系统类加载器,一般来讲我们定义的类都是由它来加载的,也是程序中的默认加载器。

在某些情况下,我们确实需要自定义加载器,如隔离加载类,修改鳄梨加载方式,防止源码泄漏等,详细内容将放在后面讲。

4. 谈谈双亲委派机制

这个我就被面试官问到过,其实我觉得这个翻译并不准确,应该叫父级代理,双亲指的并不是父母,而是指这个加载器的更高级。

流程图如下所示。

image.png

具体流程分为三步。
1.当一个类收到了加载请求,并不会直接去加载,而是由他的父类加载器去执行
2.父类加载器如果还有上级,就在向上委托,知道最上级为止
3.如果顶层的加载器能够完成加载当然最好,如果不能,再交由子类加载器去处理。

大家可以想象成上了一顿好吃的,小孩子虽然想吃,但是先推给爸爸吃,爸爸想,爷爷还没动筷呢,等爷爷吃了再吃,爷爷说,这东西太硌牙了,你们吃吧,爸爸就先吃了......

我们可以举个例子,定义一个java.lang包下的Hello类

image.png

但运行直接报错了,安全异常,被禁止的包名java.lang

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" 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.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

因为这个包下的类都是由引导类加载器管的,如果可以这么定义,那么你如果写个死循环,或者延迟的程序,可能导致加载器运行异常,这也是一种保护的措施。

5. 其他

面试题:如果判断两个class对象是否为同一个类?

首先是两个类的完整包名要都一致,且他们的加载器也要一致,第二点才答出才能够区分出你与别人的水平。

类的主动使用与被动使用

主动引用会导致类的初始化,可以回忆下第一节讲的,也就是是否会执行clinit方法。

有以下七种

  • 创建类的实例
  • 访问某个类/接口的静态变量,或为该静态变量赋值
  • 调用类的静态方法
  • 反射获取类
  • JVM启动时被表明启动的类(这个先记住吧 我也没碰到过)
  • JDK 7 开始提供的动态语言支持:
    java.lang.invoke.MethodHandle 实例的解析结果
    REF_getStatic、REF_putStatic、REF_invokeStatic 句柄对应的类没有初始化则初始化 (了解即可)

最后两种碰到的很少,了解即可。

每日分享

有的人浅薄,有的人金玉其表败絮其中。有一天你会遇到一个彩虹般绚烂的人,当你遇到这个人后,会觉得其他人都只是浮云而已。

原网站

版权声明
本文为[机智的爆爆哥]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_44353507/article/details/121851056