类加载子系统
目录:
1、内存结构概述
2、类加载器与类的加载过程
3、类加载器分类
4、ClassLoader的使用说明
5、双亲委派机制
6、其他
1、内存结构概述
首先是一个简图
简单的表述了jvm的结构,首先是class文件,由类加载子系统加载到内存中(这就是本章所讲内容),然后经过运行时数据区,然后再经过执行引擎执行,执行引擎执行的时候可能会调用本地方法(也就是c的方法)。
然后再来看一个详细的图:
这里面对每一个地方进行了更加详细的说明。
首先是类加载子系统,加载类需要经过三个阶段,首先是加载阶段,然后是链接阶段,然后是初始化阶段,然后运行时数据区中,虚拟机栈是每个单程都单独有的,pc寄存器也是每个线程单独都有的,然后执行引擎有编译器和即使编译器组成,其中即时编译器要经过中间代码生成,代码优化器,目标代码优化器的过程,然后再执行引擎执行的时候中途需要回收垃圾所以中间还用到了垃圾回收器。
注:如果我们要自己实现jvm,主要考虑的是类加载去和执行引擎。
2、类加载器与类的加载过程
因为本章笔记讲的就是类加载子系统,所以第二个标题就是类加载器与类的加载过程,主要说的是咋们类加载过程中所经历的阶段。
话不多说先上图:
先看看类加载子系统作用:
对着三点进行解释:
1、我们使用BinaryView(【点我下载此软件】)工具查看字节码文件,操作就不演示了,找一个class用工具打开就行,如下图:
我们看到了class文件的开头为CA FE BA BE,那么这就代表它是一个正确的class文件,符合规范了,如果文件开头不是这四组字母,那么说明此class文件不规范。
2、我们今天说的是类加载子系统撒,那么ClassLoader就是负责将类加载到jvm中,那么我们这个ClassLoader只负责将类加载到jvm,至于运行的时候会不会出问题,它不管。
3、加载好的类的信息会存放到运行时数据区中的方法区,其中还包括类中一些常量等信息。
然后我们来说说ClassLoader在类加载中担任的角色:
1、比如我们的Car.class,这个clas文件会被加载到jvm中,然后可能会被new很多个实例.
2、将class文件加载到jvm中后,这时被加载进去的class文件叫做DNA元数据模板,存放在运行时数据区中的方法区。
3、然后我们将class加载到jvm中需要一个过程,这个过程中需要有一个运输工具运到jvm中,那么这个运输工具就是ClassLoader。
然后继续来看类的加载过程:
首先是加载类,这个过程有三个,系统类加载器,扩展类加载器,引导类加载器。
然后需要链接,这个过程也有三个,验证、准备、解析。
然后就是初始化。
再配合一个流程图应该会更详细一点:
好,然后我们上面已经大概的说了加载过程,那么我们现在针对这个三个过程进行详解,这三个过程分别是加载,链接,初始化。
第一个过程,加载:
这讲的就是jvm加载类的三种方式。
然后就是讲的加载class文件的几种常见方式。
第二个过程,链接:
链接中有三个过程:
1、验证-> 验证class文件是否符合jvm标准,需要保证类的正确性,主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证,如果想了解详细知道细节,可以去百度,因为我也不知道,所以我就不写了。
2、准备->会为类中静态变量分配默认值,引用为空,基本类型为0这种,
需要注意的是如果类变量是final的,那么准备阶段就直接会被赋值,而不会像普通类变量一样赋默认值。
还需要注意的是,成员变量压根不会分配值,直接为null,而不是像类变量一样还有个默认值,成员变量的值会当对象被new出来后,被分配到堆中的时候才会被赋值。
3、解析->你这个还是百度百度,因为我也不会,毕竟咋们这个是笔记。
第三个过程,初始化:
初始化的过程就是执行clinit的过程,这个方法不是由我们写的,而是由jvm进行定义的。
clinit方法中操作的是:比如类变量的赋值动作,静态代码块中的代码。
构造器方法中指令执行的顺序和源码中代码的顺序是一致的,我们上面才说了,初始化会执行jvm定义的clinit方法,我们将字节码文件使用Bytecode Viewer工具查看【点我下载】,上图吧:
我们的clinit方法是对静态变量进行赋值,还执行静态代码块,那么我们源代码中显示定义a,再定义b,然后静态代码块中也是先给a赋值,再给b赋值,那么按照我们说的,证实了clinit方法中执行顺序和源代码中顺序是一致的。
clinit不同于类的构造器:clinit构造其实虚拟机定义的,用来执行静态变量赋值,静态代码块运行的,我们自己写的构造器是进行我们逻辑操作的,完全是两码事。
如果该类具有父类,jvm会保证子类的clinit执行以前,父类的clinit已经被执行完毕了,如何验证呢?(你写俩类,A和B,A的static代码中让线程中断,然后B继承A,然后你去创建B对象,此时肯定会被卡在A的static代码中,不信去试)。
虚拟机必须保证一个类的clinit方法在多线程中被同步加锁:这个啥意思勒,就是说你一个类,在多线程情况下,同时只能执行一个clinit方法,通俗点说,就是你有一个A类,在A的static块中,来个while死循环,然后你new来线程同时去创建A这个对象,你会发现,前一个线程的static代码块只会被执行一 次且卡住,然后后面的一个线程的clinit压根进不去,这就是保证了多线程情况下同一个类的clinit只能同时有一个在执行,不信去试。
3、类加载器分类
上个标题着重在讲链接和初始化的部分,这里我们来着重说一个类加载器,类加载子系统不是有三个步骤嘛,第一个就是加载class,加载class肯定就需要用类加载器来加载,我们这个标题就来细说类加载器有哪些,都有啥用。
三条:
1、JVM支持两种类型的类加载器,引导类加载器(BootStrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
2、从概念上说,自定义加载器就是我们自己写的加载器,但是实际上不是这样的,jvm规范中没有这么写,引导类加载器就是c写的那个加载器,引导类就是继承于ClassLoader的加载器(换句话说只要继承于ClassLoader,那么你就是一个自定义类加载器)/
3、就是说虚拟机无论咋分,对我们来说最常见的就是三种,引导类加载器,扩展类加载器,应用程序类加载器,其实还有一个用户自定义的加载器,但是不常见。
再来一个图看看:
然后我们知道了常见的三种类加载器,我们现在就来挨个说说:
引导类加载器:
注意,它是c写的,它只会加载jre的lib的rt.jar,resources.jar或sun.boot.class.path中的类,然后它不继承ClassLoader,因为是c写的嘛,然后处于安全考虑,只加载java、javax、sub包名开头的类。
扩展类加载器:
需要注意的是,继承与ClassLoader,是Java语言编写的。
应用程序类加载器:
用户自定义的类加载器:
实现类加载器的步骤:
4、ClassLoader的使用说明
没啥好说的,看图吧。
看图。
5、双亲委派机制
听着高大上,其实很简单!!
就比如我们的String类,它是java.lang的,应该是BootStrap进行加载的,首先是应用程序加载类拿到了,然后传到它的父亲上,一直到没有父亲为止,然后如果最上层的父亲加载了,那么就没事,如果最上层的父亲不加载这个,然后再反向回来,只能有加载器加载(放心,除非我们自定义的加载器器,否则上层父亲不要的应用程序加载器都会加载的,所以不存在不会被加载的情况)。
还有的情况就是接口需要被引导类加载器加载,但是实现类却是我们自己写的,那么我们自己携带的实现类引导加载器肯定不会加载,所以就会反向委派到我们的应用程序加载器加载。
这个报错的信息是啥意思嘞,比如我们在java.lang下写一个类,然后我们想去使用它,就会报这个错,因为为了安全起见,java.lang这种包下是不允许有其他类文件的。
还有一个沙箱安全机制:
上面我们说的我们在java.lang下写一个类,无法运行,就是因为沙箱安全机制。
6、其他
JVM判断两个Class是否为同一个类的必要条件:
类的主动加载和被动加载: