上次讲完了Class文件的格式,那么class文件该经过什么过程才能进入到内存呢?这里就用到了
类加载
加载过程 (Class Cycle)
首先讲解加载过程:
1. loading
将class文件加载到内存中
2. linking
- 1.Verification
文件校验
- 2.Preparation
静态变量附默认值
- 3.Resolution
将符号引用转化为直接地址
3. Initializing
静态变量赋值为初始值
类加载器 (ClassLoader)
类加载器也是一个普通的class文件,负责加载所有的类,并为所有加载到内存中的类生成一个生成一个java.lang.Class实例对象。已经被加载到JVM中的类具有唯一标识符,不会再被加载第二次。
我们可以通过编码:对象名.class.getClassLoader()
方法获取此对象的类加载器。
双亲委派机制
JVM是按需动态加载采用双亲委派机制
。注意这里“双亲”指的不是父母,而是指parent&child双向加载。
JVM预定义有三种类加载器,从下到上优先顺序为:
启动类加载器 BootStrap ClassLoader
↑
标准扩展类加载器 Extension ClassLoader
↑
应用类加载器 Application ClassLoader
在实际加载过程中最底层还是由用户自定义类加载器 Custom ClassLoader
开始。下图展示了每层类加载器的职责内容和加载方向。
类加载器选择和加载流程:
通过此图可以直观明了的看出来类加载器选择和加载的过程,我简单说明一下:
当某个.class文件要被加载时,首先会从底层类加载器开始查找是否被加载,如果有就返回,没有就向它的上一层父加载器里查找,没有就继续往上直到BootStrap ClassLoader
。
若是直到BootStrap ClassLoader
也没有找到,就向下进行委托加载该类,此加载器若可以加载就执行并返回,若不能加载就继续向下委托,直到自定义类加载器。
但如果到自定义类也无法加载,就会抛出异常:ClassNotFound。通过双亲委派机制可以保证程序编译后class文件的安全性。
这里要注意父加载器不是类加载器的加载器,也不是类加载器的父类加载器。父加载器只是加载器的一个方向说明。在编程中可以通过对象名.class.getClassLoader().getParent()
方法调用。
类加载器范围
java中类加载器的范围在Launcher
的源码中。分别为:
- sun.boot.class.path
Bootstrap ClassLoader加载路径 - java.ext.dirs
- Extension ClassLoader加载路径
- java.class.path
- Application ClassLoader加载路径
通过编码:System.getPreperty("类加载器路径")
查看本地类加载器的文件范围。
public class ClassLoaderPath {
public static void main(String[] args) {
String path;
System.out.println("------------------------------------");
path = System.getProperty("sun.boot.class.path");
System.out.println(path.replaceAll(";", System.lineSeparator()));
System.out.println("------------------------------------");
System.out.println("------------------------------------");
path = System.getProperty("java.ext.dirs");
System.out.println(path.replaceAll(";", System.lineSeparator()));
System.out.println("------------------------------------");
path = System.getProperty("java.class.path");
System.out.println(path.replaceAll(";", System.lineSeparator()));
}
}
运行结果如图:
自定义类加载器
自定义类加载器要继承ClassLoader
,并重写。一般要用到findClass()
,defineClass(byte[] -> Class 对象名)
。在要用到的地方只需调用对象.loadClass("自定义类加载器名")
就可以,其返回的是Class类型对象。
什么时候需要自定义类加载器?
- 加密:知道了class的编译过程就知道java代码很容易被反编译,如果你需要把自己的代码进行加密,可以先将编译后的代码用某种加密算法加密,然后实现自己的类加载器,负责将这段加密后的代码还原。
- 从非标准的来源加载代码:例如你的部分字节码是放在数据库中甚至是网络上的,就可以自己写个类加载器,从指定的来源加载类。
- 动态创建、服务器热部署:为了性能等理由,根据实际情况动态创建代码并执行。
编译器
编译器主要包括:解释器和编译。Java语言在默认情况下使用的是混合模式
。
- 解释器
bytecode interpreter- -Xint使用纯解释模式,启动很快,执行稍慢
- JIT
Just-In Time compiler- -Xcomp使用纯编译模式,执行很快,启动较慢
- 混合模式
混合使用解释器和热点代码编译- -Xmixed默认混合模式,启动和热点代码编译都比较快
可以通过修改Run的设置来选择编译模式。
懒加载
Lazyloading(严格意义上应为LazyInitializing)
JVM没有规定什么时候加载,只有被用到的时候才会加载,类似懒加载模式。但JVM严格规定了什么时候必须初始化。
————————————————————————————————
补充:
父类静态代码块–>子类静态代码块–>父类普通代码块–>父类构造方法–>子类代码块–>子类构造方法;
Java程序初始化顺序:
1.父类的静态代码块
2.子类的静态代码块
3.父类的普通代码块
4.父类的构造方法
5.子类的普通代码块
6.子类的构造方法