用WordPress建什么站好,电商运营主要工作内容,淄博网站建设推广乐达,军事新闻头条最新消息当程序主动使用某个类时#xff0c;如果该类还未加载到内存中#xff0c;则系统会通过如下三个步骤来对该类进行初始化#xff1a; 1.加载#xff1a;将class文件字节码内容加载到内存中#xff0c;并将这些静态数据转换成方法区的运行时数据结构#xff0c;然后生成一个…当程序主动使用某个类时如果该类还未加载到内存中则系统会通过如下三个步骤来对该类进行初始化 1.加载将class文件字节码内容加载到内存中并将这些静态数据转换成方法区的运行时数据结构然后生成一个代表这个类的java.lang.Class对象
2.链接Java类的二进制代码合并到JVM的运行状态之中的过程。
(1)验证确保加载的类信息符合JVM规范没有安全方面的问题
(2)准备正式为类变量(static)分配内存并设置类变量默认初始值的阶段这些内存都将在方法区中进行分配(static是在内初始化之前就完成了)
(3)解析虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。()
3.初始化:
(1)执行类构造器clinit()方法的过程类构造器clinit()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的不是构造该类对象的构造器)(JVM去完成的)
(2)当初始化一个类的时候如果发现其父类还没有进行初始化则需要先触发其父类的初始化
(3)虚拟机会保证一个类的clinit()方法在多线程环境中被正确加锁和同步。
总结
虚拟机把Class文件加载到内存并对数据进行校验、转换解析和初始化形成可以虚拟机直接使用的Java类型即java.lang.Class如下图 接下来会对该三个步骤逐一进行解释。 一.类的加载 作用查找和导入Class文件。
步骤
1.通过一个类的全限定名获取定义此类的二进制字节流(那么这个时候需要一个寻找器来寻找获取我们的二进制字节流而java中恰好有这么一段代码模块可以实现通过类全名来获取此类的二进制字节流这个动作并且将这个动作放到放到java虚拟机外部去实现以便让应用程序决定如何获取所需要的类实现这个动作的代码模块成为“类加载器”)
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在Java堆中生成一个代表这个类的java.lang.Class对象作为对方法区中这些数据的访问入口。(此时静态数据结构已经放进了方法区但是此时我们没有办法去进行访问java当中去访问数据的方法是通过引用去操作对象然后通过对象去操作数据所以还需要再堆当中去生成一个代表代表这个类的java.lang.Class对象作为方法区中的数据访问入口)
在加载阶段完成之后这个时候在内存当中运行时数据区的方法区以及堆就已经有数据了
(1)方法区类信息静态变量常量
(2)堆代表被加载类的java.lang.Class对象。
及时编译之后的热点代码并不在这个阶段进入方法区。
类加载器
类加载器作用是用来把类(class)装载进内存的JVM 规范定义了如下类型的类的加载器 二.类的链接 1.验证
作用
验证只要是为了确保Class文件中的字节流包含的信息完全符合当前虚拟机的要求并且还要求我们的信息不会危害虚拟机自身的安全导致虚拟机的崩溃。
验证内容
文件格式的验证、元数据验证、字节码的验证、符号引用的验证。
2.准备
作用
为类的静态变量分配内存并且初始化为当前类型的默认值。
解释
1.这里不包含用final修饰的static因为final在编译的时候就会分配了准备阶段会显式初始化这里不会为实例变量(也就是没加static)分配初始化类变量会分配在方法区中而实例变量是会随着对象一起分配到Java堆中
2.进行分配内存的只是包括类变量(静态变量)而不包括实例变量实例变量是在对象实例化时随着对象一起分配在java堆中的通常情况下初始值为零值假设public static int a1那么a在准备阶段过后的初始值为0不为1这时候只是开辟了内存空间并没有运行java代码a赋值为1的指令是程序被编译后存放于类构造器()方法之中所以a被赋值为1是在初始化阶段才会执行。
3.解析
作用
把类中的符号引用转换为直接引用
(1)符号引用就是一组符号来描述目标可以是任何字面量
(2)直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
(因为再一个Class文件中没有办法去表示引用关系只能告诉你引用到10行或者20行这种直接应用就表示你执行的位置在内存当中有一块具体的地址比如说a指向b是符号应用a指向b所在的位置0x01在内存中这叫直接引用)
解释
(1)解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行
(2)直接应用是与虚拟机内存布局相关的同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般是不相同的如果有了直接引用那引用的目标必定存在内存中
(3)类缓存标准的JavaSE类加载器可以按要求查找类但一旦某个类被加载到类加载器中它将维持加载(缓存)一段时间。不过VM垃圾回收机制可以回收这些Class对象。(同一符号引用进行多次解析请求是很常见的除invokedynanic指令以外虚拟机实现可以对第一次解析结果进行缓存来避免解析动作重复进行无论是否真正执行了多解析动作虚拟机需要保证的是在同一个实体中如果一个引用符号之前已经被成功解析过那么后续的引用能析请求就应当一直成功同样的如果第一次解析失败那么其他指令对这个符号的解析请求也应该收到相同的异常)
(4)inDy(invokedynamic)是java7引入的一条新的虚拟机指令这是自 1.0 以来第一次引入新的虚拟机指令到了 java 8 这条指令才第一次在java 应用用在 lambda 表达式中indy 与其他 invoke 指令不同的是它允许由应用级的代码来决定方法解析。 三.类的初始化 作用
初始化阶段是执行类构造器Clinit()方法的过程或者讲得通俗易懂些加载和链接以外的工作都需要在初始化中去完成。
在准备阶段类变量已赋过一次系统要求的初始值而在初始化阶段则是根据自己通过程序制定的主观计划去初始化变量和其他资源比如赋值。
什么时候发生类初始化
1.类的主动引用(一定会发生类的初始化)
(1)当虚拟机启动先初始化main方法所在的类
(2)new一个类的对象
(3)调用类的静态成员(除了final常量)和静态方法
(4)使用java.lang.reflect包的方法对类进行反射调用
(5)当初始化一个类如果其父类没有被初始化则先会初始化它的父类
2.类的被动引用(不会发生类的初始化)
(1)当访问一个静态域时只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量不会导致子类初始化
(2)通过数组定义类引用不会触发此类的初始化
(3)引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)。
在Java中对类变量进行初始值设定有两种方式:
1.声明类变量是指定初始值
2.使用静态代码块为类变量指定初始值。
按照程序逻辑必须把静态变量定义在静态代码块的前面因为两个的执行是会根据代码编写的顺序来决定的顺序搞错了可能会影响你的业务代码。
JVM初始化步骤:
1.假如这个类还没有被加载和连接则程序先加载并链接该类
2.假如该类的直接父类还没有被初始化则先初始化其直接父类
3.假如类中有初始化语句则系统依次执行这些初始化语句。 四.举例 代码如下
public class Test {public static void main(String[] args) {A a new A();System.out.println(A.m);/*1.加载到内存 会产生一个类对应class对象2.链接 链接结束后 m 03.初始化 m 100:clinit(){System.out.println(A类静态代码块初始化”);m 300;m100}*/}
}class A {static {System.out.println(A类静态代码块初始化);m 300;}static int m 100;public A() {System.out.println(A类的无参构造初始化);}
}输出结果
A类静态代码块初始化
A类的无参构造初始化
100 大概流程如下 首先在方法区产生了一些该类的静态数据然后在加载的类的时候就产生了对应的class然后看main()方法main()方法后就开始链接此时m有一个初始值0等链接完没有问题的时候就开始执行代码此时new A()就产生了一个A类的对象这个对象就会去找到自己的Class类(指向)通过A类的数据结构给A类对象赋值(拿到数据)赋值完成后通过clinit()方法初始化数据得到m等于100。