文章目录
类加载子系统
- 类加载器负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识
- ClassLoader只负责class文件的加载,至于是否可以运行,则由Execution Engine决定
- 加载类信息存放于一块称为方法区的内存空间。除了类信息外,方法区还会存放运行时常量池的信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
类加载器ClassLoader角色
- Class File存在本地硬盘上,经过类加载器后,最终要实例化出n个一模一样的实例
- Class File加载到JVM中,被称为DNA元数据模板,放在方法区
- .Class文件—>JVM—>最终成为元数据模板,此过程就要一个类加载器
类加载过程
加载(Loading)—>链接(Linking)【验证、准备、解析】—>初始化(Initialization)
加载
- 通过一个类的全限定名获取此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
链接
-
验证
- 主要验证当前加载的字节码文件符合JVM的规范,不会对虚拟机产生损害
- 主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
-
准备
- 为类变量分配内存并设置默认初始值,即零【private static int a = 1; prepare:a=0—>initial:a=1】
- 不包含final修饰【常量,是在常量池中】的static变量,因为final在编译时就会分配,准备阶段会显示初始化
- 不会为Java实例变量分配初始化,类变量会分配到方法区中,而实例变量会随着对象一起分配到Java堆中
-
解析
- 将常量池的符合引用转为直接引用
- 事实上,解析操作会伴随着JVM初始化之后操作
- 符合引用就是一组符合来描述所引用的目标
- 解析主要针对类或者接口、字段、接口方法、方法类型等
初始化
-
初始化阶段就是执行构造器方法()的过程
-
此方法不用定义,是由javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来的
-
构造器方法中指令按语句在源文件中出现的顺序执行
-
()不同于类的构造器
-
若该类具有父类,会先执行父类的()
-
JVM必须保证一个类的()在多线程下同步加锁
-
public class Static { private static int a = 5; static { a = 10; b = 50; System.out.println(a); // System.out.println(b);//报错,非法前向引用 } private static int b = 20; public static void main(String[] args) { System.out.println(a);//10 System.out.println(b);//20 静态变量 prepare分配内存并赋默认值b=0 initial赋值50-->20 } }
-
任何一个类声明以后,都会有一个类构造器()
- 当声明静态变量时会出现()
类加载器的分类
- JVM支持两种类型的类加载器,分别是引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)
- JVM对自定义类加载器的定义,将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
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
ClassLoader stringLoader = String.class.getClassLoader();
System.out.println(stringLoader);//引导类加载器 null
}
}
虚拟机自带的加载器
启动类加载器(引导类加载器 Bootstrap ClassLoader)
- 此类加载器一般使用c/c++实现,嵌套在JVM内部
- 用来加载JVM的核心类库(rt.jar,resources.jar…),用于提供JVM自身需要的类
- 不继承java.lang.ClassLoader,没有父加载器
- 加载扩展类和应用程序类加载器,并指定为它们的父加载器
- 处于安全考虑,Bootstrap启动类加载器包名为java、javax、sum等开头的类
sun.misc.Launcher是Java虚拟机的一个入口应用
扩展类加载器
- Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
- 派生与ClassLoader类
- 父加载器为启动类加载器
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录jre/lib/ext子目录(扩展目录)下面加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
应用程序类加载器
- Java语言编写,由sun.misc.Launcher$AppClassLoader实现
- 派生与ClassLoader类
- 父加载器为启动类加载器
- 它负责环境变量classpath或系统属性java.class.path指定路径下的类库
- 该类加载是程序中默认的加载器,一般来说,Java应用的类都是由他来加载的
- 通过ClassLoader#getSystemClassLoader()可以获取到该类加载器
public class ContainClassLoader {
public static void main(String[] args) {
//引导类加载器中加载的api的url
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(urL);
}
//jce.jar中的.class文件,其类加载器是Bootstrap
ClassLoader classLoader = TlsMasterSecret.class.getClassLoader();
System.out.println(classLoader);//null
//扩展类加载器中加载的api的url
String property = System.getProperty("java.ext.dirs");
for (String s : property.split(";")) {
System.out.println(s);
}
ClassLoader classLoader1 = EventID.class.getClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d
}
}
用户自定义类加载器
在Java日常开发中,类的加载器几乎是由上述3种类加载器相互配合完成的。在必要时,我们还可以自定义类加载器,来定制类的加载方式
为什么要自定义类的加载器?
- 隔离加载类
- 修改类加载方式
- 扩展加载源
- 放在源码泄露
用户自定义加载器的实现步骤:
- 继承抽象类CalssLoader
- JDK1.2之前重写loadClass(),JDK1.2之后将自己的加载逻辑写在findClass()
- 如果没有太过于复杂的需求可以直接继承URLClassLoader类,这样可以避免自己去编写findClass()以及获取字节码流的方式,是自定义类加载器更加简介
获取加载器的方法
- clazz.getClassLoader()
- Thread.currentThread().getContextClassLoader()
- ClassLoader.getSystemClassLoader()
- DriverManager.getCallerClassLoader()
public class GetClassLoader {
public static void main(String[] args) {
//1.
try {
Class<?> aClass = Class.forName("java.lang.String");
ClassLoader classLoader = aClass.getClassLoader();//null
System.out.println(classLoader);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//2.
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader);//sys
//3.
ClassLoader classLoader1 = ClassLoader.getSystemClassLoader().getParent();
System.out.println(classLoader1);//ext
}
}
双亲委派机制
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将他的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派机制,即把请求交由父类处理,它是一种任务委派模式
优势
- 避免类的重复加载
- 保护程序安全,防止核心API被随意篡改
工作原理
- 如果一个类加载器收到加载一个类的请求,他不会自己先去加载,会先交给父加载器去加载
- 如果父加载器还有父加载器,那么会继续向上委托,直到到引导类加载器
- 如果父加载器可以完成类加载任务,那么就由父加载器完成,如果不能,子类加载器才会去尝试完成,这就是双亲委派机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ZVqUgeK-1617500020966)(JVM.assets/image-20210401173224214.png)]
报错原因:首先会一直向上请求到引导类加载器,去加载此类,此时引导类加载器可以加载String类,完成请求,但是引导类加载器的String没有main(),因此会报错
报错原因:访问java.lang 包是要权限的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xBppUB6G-1617500020968)(JVM.assets/image-20210401175221903.png)]
沙箱安全机制
对于自定义的java.lang.String.class 在加载的过程中还是会先去加载rt.jar包中的String但是此jar包没有main(),这样就可以保证对java核心源码的保护,这就是沙箱安全机制