JVM类加载机制
类加载子系统
类加载过程
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。(验证、准备和解析又统称为连接,为了支持Java语言的运行时绑定,所以解析阶段也可以是在初始化之后进行的。以上顺序都只是说开始的顺序,实际过程中是交叉的混合式进行的,加载过程中可能就已经开始验证了)
加载(Loading):
通过一个类的全限定名来获取该类的二进制字节流。
将这个字节流的所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成这个类的class对象,作为方法区这个类的各种数据的访问入口。
连接(Linking):
验证(Verify):
确保class文件字节流中包含的信息符合当前虚拟机的要求,保证被加载类的正确性。
主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证
准备(Prepare):
为静态变量分配内存并设置默认初始值,即零值。
常量(final static)在编译的时候就已分配。
不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中
privatestaticinti =1;//变量i在准备阶只会被赋值为0,初始化时才会被赋值为1privatefinalstaticintj =2;//这里被final修饰的变量j,直接成为常量,编译时就会被分配为2
解析(Resolve):
将常量池内的符号引用转换为直接引用的过程。
解析操作往往会伴随着JVM在执行完初始化之后再执行
初始化(Initialization):
初始化阶段就是执行类构造器方法<clinit>()的过程
此方法不需要定义,是javac编译器自动收集类中的所有静态变量的赋值动作和静态代码块中的语句合并而来
构造器方法中指令按语句在源文件中出现的顺序执行
若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕
虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁
publicclasstest{privatestaticintnum1 =30; static{ num1 =10; num2 =10;//num2写在定义变量之前,为什么不会报错呢??System.out.println(num2);//這裡直接打印可以吗?报错,非法的前向引用,可以赋值,但不可调用}privatestaticintnum2 =20;//num2在准备阶段就被设置了默认初始值0,初始化阶段又将10改为20publicstaticvoidmain(String[] args){ System.out.println(num1);//10System.out.println(num2);//20}}
哪些情况能触发类的初始化阶段?(主动和被动使用)
主动使用(5种)
创建类的实例、访问某个类或接口的静态变量,或者对该静态变量赋值、调用类的静态方法(即遇到new、getstatic、putstatic、invokestatic这四条字节码指令时)
反射
初始化类的时候如果其父类还没进行初始化,则需要先触发父类的初始化
java虚拟机启动时被标明为启动类(包含main方法的那个类)的类
JDK7 开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果,REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化
被动使用
除以上五种情况,其他使用Java类的方式被看作是对类的被动使用,都不会导致类的初始化。
publicclassNotInitialization{publicstaticvoidmain(String[] args){//只输出SupperClass int 123,不会输出SubClass init//对于静态字段,只有直接定义这个字段的类才会被初始化System.out.println(SubClass.value); }}classSuperClass{static{ System.out.println("SupperClass init"); }publicstaticintvalue=123;}classSubClassextendsSuperClass{static{ System.out.println("SubClass init"); }}
类加载器
启动类加载器
使用C/C++ 语言实现,嵌套在JVM 内部
并不继承自 java.lang.ClassLoader,没有父加载器
负责加载<JAVA_HOME>\lib 目录下的类
扩展类加载器
java语言编写,由 sun.misc.Lanucher$ExtClassLoader实现
派生于 ClassLoader
负责加载<JAVA_HOME>\lib\ext 目录中class。
应用程序类加载器
java语言编写,由 sun.misc.Lanucher$AppClassLoader实现
派生于 ClassLoader
自定义类加载器
双亲委派机制
按需加载,将加载类的请求交给父类处理,倘若父类加载不了,则由子类加载器尝试自己完成加载,这就是双亲委派模式。
优势
避免类的重复加载
保护程序安全,防止核心API被随意篡改,避免用户自己编写的类动态替换 Java的一些核心类,比如我们自定义类:java.lang.String。
是否为同一个对象
比较是否同一个类加载器,然后再比较对象是否相等。
参考资料
https://yq.aliyun.com/articles/745762?spm=5176.10695662.1996646101.searchclickresult.2cf95455FQyq8b
网友评论