一 内存结构
二 类加载过程
类加载系统负责从文件系统,网络等中加载字节码文件,字节码文件是有特定的头部标识的(CA FE BA BE)
2.1 加载(loading)
1)通过一个类的全限定名来获取类的字节类。
2)然后将这个字节流静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个java.lang.Class类的对象,作为方法区中这个类的各种数据结构的访问入口。
2.2 链接(linking)
2.2.1 验证过程(verification)
1)这个阶段的目的是在于确保class字节码文件字节流中包含的信息符合当前jvm规范,保证被加载的类的准确性,不会危害到jvm本身。
2)验证方式有四种:文件格式验证,元数据验证,字节码验证,符号验证。
2.2.2 准备阶段(preparation)
1)这个阶段是已经开始在内存中为类的结构分配位置了,首先给类变量分配内存,并且给类变量默认初始化,设为零值。
2)这个默认初始化的静态类变量不包括被final修饰的静态变量,因为final在编译的时候就已经被分配好了,这时候在这个阶段会进行显式的初始化值。
3)这个阶段也不会为实例变量进行初始化,因为类变量会被分配到方法区,而实例变量则会和对象一起被分配到Java堆中。
2.2.3 解析阶段(revolution)
1)将常量池中的符号引用转化为直接引用的过程。
2)事实上,解析操作通常是伴随着jvm的类加载过程的初始化阶段结束之后才进行解析的。
2.3 初始化(initialization)
1)初始化阶段就是执行类构造器方法
2)
3)
4)
5)如果被加载的类有父类,则会保证在子类的
6)虚拟机会保证在多线程的情况下,被加载类的
三 类加载器
3.1 引导类加载器
1)负责加载Java核心代码的类库,比如 /jre/lib/rt.jar,/resource.jar等,这些包下的类都是被引导类加载器所加载的。
2)由C/C++来实现的,镶嵌在Java虚拟机中,所以并不继承java中的java.lang.ClassLoader类,没有父类加载器。
3)额外负责加载扩展类加载器和系统类加载器类,并指定为它们的父类加载器。
4)处于安全考虑,引导类加载器只会加载开头为java,javax,sun开头的类。
3.2 扩展类加载器
- 负责加载jre包下的扩展类,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包。
Java实现:sun.misc.Launcher$ExtClassLoader。
3.3 系统类加载器
负责加载开发者编写的类和第三方的jar包。
Java实现:sun.misc.Launcher$AppClassLoader。
3.4 自定义类加载器
1)可以自定义开发自己的类加载器,来加载特定的类。
2)原因:在Java的日常开发中,类的加载几乎都是由那三种类加载器相互配合完成,必要时,可以自定义一个类加载器,来定制类的加载方式。
3)自定义类加载器有如作用:①隔离加载类,②修改类加载的方式,③扩展加载源,④防止源码泄露
4)开发步骤直接继承 java.lang.ClassLoader,实现自己的类加载器。
- 建议把加载类的逻辑代码写到 findClass()方法中。
- 如果没有特定的需求,还可以直接继承URLClassLoader类,可以省去findClass()方法的重写和获取字节流的方式。
四 ClassLoader类
1)ClassLoader类是一个抽象类,其后的所有类加载器都可以继承自ClassLoader(不包括启动类加载器)
2)获取到 ClassLoader 的途径
获取当前类的ClassLoader
clazz.getClassLoader();
获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader();
获取系统的ClassLoader
ClassLoader.getSystemClassLoader();
获取调用者的ClassLoader
DriverManager.getCallerClassLoader();
五 双亲委派机制
5.1 概述
1)Java虚拟机class文件采用的是按需加载的方式,即是在需要使用到该类的时候,才会将该类加载到内存中生成java.lang.Class类的对象。
2)而且加载类的时候,Java虚拟机采用的是一种双亲委派机制模式,即是把需求上交给父类加载器,它是一种任务委派机制。5.2 工作原理
1)如果一个类加载器接收到加载类的任务,它不会立即去加载此类,而是将此任务上递交给它的父类加载器,同时它的父类加载器也是如此。
2)它的父类也如此递归的将要加载的类上交给自己的父类加载器,直到到达了引导类加载器那里。
3)引导类加载器会尝试去加载此类,如果不能加载,则会把任务返回给上交任务给它的类加载器,直到找到可以加载这个类的类加载器。
4)可以从上面工作原理知道,双亲委派机制就是首先从最上面的类加载器来加载类,如果父类加载器可以加载,则就成功结束了。
5)这样子的优势就是可以防止类被重复加载,只有一个类加载器可以加载当前类。而且保护了程序的安全,防止程序的核心api被随意篡改。// 沙箱安全机制
packge java.lang
public class String{
public static void main(String[] args){
System.Out.print("自定义的String类,包名和Java源码的String类包名一样");
// java.lang.SecurityException: Prohibited package name: java.lang
}
}
/**
* 自定义的String类和jdk下的String类名一样,包名一样,那么类加载器该如何工作呢?
* 这时候,系统类加载器会首先收到任务,然后上交直到引导类加载器那里,
* 引导类加载器一看,这个String类包名是和jdk一样,然后就直接去jdk下加载String类了。
* 但是自定义的String中有一个main方法,程序运行的时候,类加载JDK下的String类找不到main方法,
* 程序就报错了,找不到main方法
*/
六 其他
6.1 判断两个Class实例是否属于同一个类
1)类的完整路径必须是一致的,包括包名。
2)加载这个类的ClassLoader(ClassLoader实例对象)必须相同。
3)也就是说,在JVM中,即使这两个Class实例来源同一个Class文件,被同一个虚拟机所加载,但是只要加载它们的类加载器对象不同,那么这两个对象也是不等的。6.2 类主动使用和被动使用
1)主动使用,分为7种情况
- 创建类的实例
- 访问类或者接口的静态变量,或者对静态变量赋值
- 调用类的静态方法
- 反射
- 初始化一个类的子类
- JVM启动时被标明为启动类的类
- JDK7开始提供的动态语言支持:
- java.lang.invoke.MethodHandler实例的解析结果
- REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化
2)除了上面七种主动使用情况,其他使用Java类的方式都可以看作时对类的被动使用,都不会导致类的初始化。