网站建设哪家公司,如何用dw做网站前端,上海广告公司排名前十强,手机网站如何开通微信公众号首先#xff0c;请区分Bean的声明周期和类的声明周期。此处讲的是类的声明周期 可以同步观看另一篇文章JVM之【类加载机制】
概述 在Java中数据类型分为基本数据类型和引用数据类型 基本数据类型由虚拟机预先定义#xff0c;引用数据类型则需要进行类的加载 按照]ava虚拟机…首先请区分Bean的声明周期和类的声明周期。此处讲的是类的声明周期 可以同步观看另一篇文章JVM之【类加载机制】
概述 在Java中数据类型分为基本数据类型和引用数据类型 基本数据类型由虚拟机预先定义引用数据类型则需要进行类的加载 按照]ava虚拟机规范从class文件到加载到内存中的类到类卸载出内存为止它的整个生命周期包括如下7个阶段其中验证、准备、解析 3个部分统称为链接Linking。 从程序中类的使用过程看: 1、加载Loading
需要注意的是类型元数据、类模板对象、Java类模型指的是同一个概念
1加载的理解
所谓加载简而言之就是将Java类的字节码文件加载到机器内存中并在内存中构建出Java类的原型——类模板对象。所谓类模板对象其实就是Java类在JVM内存中的一个快照JVM将从字节码文件中解析出的常量池、类字段、类方法等信息存储到类模板中这样JM在运行期便能通过类模板而获取]ava类中的任意信息能够对Java类的成员变量进行遍历也能进行Java方法的调用。反射的机制即基于这一基础。如果JVM没有将Java类的声明信息存储起来则JVM在运行期也无法反射。
(2二进制流的获取方式
对于类的二进制数据流虚拟机可以通过多种途径产生或获得。(只要所读取的字节码符合JVM规范即可)
虚拟机可能通过文件系统读入一个class后缀的文件(最常见)读入jar、zip等归档数据包提取类文件。事先存放在数据库中的类的二进制数据使用类似于HTTP之类的协议通过网络进行加载在运行时生成一段Class的二进制信息等 在获取到类的二进制信息后Java虚拟机就会处理这些数据并最终转为一个Java.Lang.Class的实例。如果输入数据不是ClassFile的结构则会抛出ClassFormatError。 3加载完成的操作
加载阶段简言之查找并加载类的二进制数据生成Class的实例。在加载类时Java虚拟机必须完成以下3件事情 通过类的全名获取类的二进制数据流。解析类的二进制数据流为方法区内的数据结构(类模板对象)在堆中创建Java.Lang.Class类的实例表示该类型。作为方法区这个类的各种数据的访问入口
4类模型和Class类的位置
这里其实就是类模型/类模板/类元数据和Class类的区别
类模型的位置
加载的类在JVM中创建相应的类结构类模型会存储在方法区(JDK1.8之前:永久代;JDK1.8及之后:元空间)。
Class实例的位置
类将.class文件加载至元空间后会在堆中创建一个Java.Lang.Class对象用来封装类位于方法区内的数据结构该Class对象是在加载类的过程中创建的每个类都对应有一个Class类型的对象。
外部可以通过访问代表Order类的Class对象来获取Prder的类数据结构 5再说明
Class类的构造方法是私有的只有JVM能够创建Java.Lang.Class实例是访问类型元数据的接口也是实现反射的关键数据、入口。通过class类提供的接口可以获得目标类所关联的.class文件中具体的数据结构:方法、字段等信息。 2、链接Linking
验证、准备、解析 3个部分统称为链接Linking。
1验证Verification
当类加载到系统后就开始链接操作验证是链接操作的第一步。它的目的是保证加载的字节码是合法、合理并符合规范的。
验证的步骤比较复杂实际要验证的项目也很繁多大体上Java虚拟机需要做以下检查如图所示。
整体说明:
验证的内容则涵盖了类数据信息的格式验证、语义检査、字节码验证以及符号引用验证等。其中格式验证会和加载阶段一起执行。验证通过之后类加载器才会成功将类的二进制数据信息加载到方法区中。格式验证之外的验证操作将会在方法区中进行。链接阶段的验证虽然拖慢了加载速度但是它避免了在字节码运行时还需要进行各种检査。(磨刀不误砍柴工)
具体说明:
1、格式验证 是否以魔数 OxCAFEBABE开头主版本和副版本号是否在当前Java虚拟机的支持范围内数据中每一个项是否都拥有正确的长度等。 2、Java虚拟机会进行字节码的语义检查但凡在语义上不符合规范的虚拟机也不会给予验证通过 是否所有的类都有父类的存在(在]ava里除了object外其他类都应该有父类)是否一些被定义为final的方法或者类被重写或继承了非抽象类是否实现了所有抽象方法或者接口方法是否存在不兼容的方法(比如方法的签名除了返回值不同其他都一样这种方法会让虚拟机无从下手调度;abstract情况下的方法就不能是final的了) 3、Java虚拟机还会进行字节码验证字节码验证也是验证过程中最为复杂的一个过程。它试图通过对字节码流的分析判断字节码是否可以被正确地执行 在字节码的执行过程中是否会跳转到一条不存在的指令函数的调用是否传递了正确类型的参数变量的赋值是不是给了正确的数据类型等 栈映射帧(StackMapTable)就是在这个阶段用于检测在特定的字节码处其局部变量表和操作数栈是否有着正确的数据类型。但遗憾的是100%准确地判断一段字节码是否可以被安全执行是无法实现的因此该过程只是尽可能地检査出可以预知的明显的问题。如果在这个阶段无法通过检查虚拟机也不会正确装载这个类。但是如果通过了这个阶段的检查也不能说明这个类是完全没有问题的。 在前面3次检查中已经排除子文件格式错误、语义错误以及字节码的不正确性。但是依然不能确保类是没有问题的。
校验器还将进行符号引用的验证 Class文件在其常量池会通过字符串记录自己将要使用的其他类或者方法。因此在验证阶段虚拟机就会检查这些类或者方法是否确实是存在的并且当前类有权限访问这些数据如果一个需要使用类无法在系统中找到则会抛出NoClassDefFoundError,如果一个方法无法被找到则会抛出NoSuchMethodError。此阶段在解析环节才会执行。
2准备Preparation
简言之为类的静态变量分配内存并将其初始化为默认值。
是静态变量
当一个类验证通过时虚拟机就会进入准备阶段。在这个阶段虚拟机就会为这个类分配相应的内存空间并设置默认初始值。Java虚拟机为各类型变量默认的初始值如表所示
注意Java并不支持boolean类型对于boolean类型内部实现是int由于int的默认值是0故对应的boolean的默认值就是false。
注意:
这里不包含基本数据类型的字段用static final修饰的情况因为final在编译的时候就会分配了准备阶段会显式赋值。这里详情可见Class字节码章节其中常量池中讲到常量池中主要有两类一类是字面量一类是符号引用。而字面量有两类一类是字符串另一类就是被final所修饰的常量。注意这里不会为实例变量分配初始化类变量static会分配在方法区中而实例变量是会随着对象一起分配到Java堆中new的事后。在这个阶段并不会像初始化阶段中那样会有初始化或者代码被执行。 对上面中的第1点分析 注意:以下前3点的前提都是字段已经完成显示赋值(定义的后面已经赋了值就是static int a 1;有号进行赋值)的前提下进行的 非final修饰的静态变量会在准备阶段赋初始值然后在初始化中的方法中显示赋值静态常量(基本数据类型、String类型字面量(String str “XXXX”; 这种情况))在编译阶段会初始化赋值然后在准备阶段就会显示赋值引用数据类型的静态常量尤其是String str new String(“XX”)这种形式都是在初始化中的中进行显示赋值的。注意和2中的String字面量区分这里是通过new得到的而不是直接字面量赋值。如果在static静态代码块中具有显示赋值操作那肯定就是在初始化中的方法中显示赋值。 3解析Resolution
简言之将类、接口、字段和方法的符号引用转为直接引用
如何理解符号引用和直接引用个人经验不保真 符号引用就好比是一个菜谱中写着的食材名称如大米字段或者烹饪机器如电饭锅方法。在食谱中不需要知道任何额外信息只需要知道这里会用到一个叫大米/电饭锅的东西。无所谓在哪能拿到它也无所谓电饭锅有什么用。
直接引用就好比是烹饪的过程要把食谱中的大米二字这一个指代性名词变成实实在在的物品。这里可以理解为一个指针即告诉你大米在哪你可以去那里直接获取。同时还需要把电饭锅这一指代性名词变成实实在在的物品也是一个指针你去那里那里有电饭锅直接用就行。
1具体描述:
符号引用就是一些字面量的引用和虚拟机的内部数据结构和和内存布局无关。比较容易理解的就是在class类文件中通过常量池进行了大量的符号引用。但是在程序实际运行时只有符号引用是不够的比如当如下printin()方法被调用时系统需要明确知道该方法的位置。
举例
// 输出操作
System.out.println();
// 对应的字节码:
invokevirtual #24 java/io/PrintStream.println以方法为例Java虚拟机为每个类都准备了一张方法表将其所有的方法都列在表中当需要调用一个类的方法的时候只要知道这个方法在方法表中的偏移量就可以直接调用该方法。通过解析操作符号引用就可以转变为目标方法在类中方法表中的位置从而使得方法被成功调用。 3、初始化
简言之为类的静态变量赋予正确的初始值。
1具体描述 类的初始化是类装载的最后一个阶段。如果前面的步骤都没有问题那么表示类可以顺利装载到系统中。此时类才会开始执行Java字节码。(即:到了初始化阶段才真正开始执行类中定义的 Java 程序代码。) 初始化阶段的重要工作是执行类的初始化方法clinit()方法
该方法仅能由Java编译器生成并由JVM调用程序开发者无法自定义一个同名的方法更无法直接在Java程序中调用该方法虽然该方法也是由字节码指令所组成。它是由类静态成员的赋值语句以及static静态代码块合并产生的
2说明
在加载一个类之前虚拟机总是会试图加载该类的父类因此父类的clinit()总是在子类clinit()之前被调用也就是说父类的static块优先级高于子类。Java编译器并不会为所有的类都产生clinit()初始化方法。哪些类在编译为字节码后字节码文件中将不会包含clinit()方法? 1一个类中并没有声明任何的类变量也没有静态代码块时2一个类中声明类变量但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时3一个类中包含static final修饰的基本数据类型的字段这些类字段初始化语句采用编译时常量表达式
// 不会包含clinit()方法的三种情况
// 1、
public int a 1;
// 2、
public static int a;
//3、
public static final int a 1;3多线程环境的初始化方法
对于clinit()方法的调用也就是类的初始化虚拟机会在内部确保其多线程环境中的安全性。虚拟机会保证一个类的clinit()方法在多线程环境中被正确地加锁、同步如果多个线程同时去初始化一个类那么只会有一个线程去执行这个类的clinit()方法其他线程都需要阻塞等待直到活动线程执行clinit()方法完毕。正是因为函数c1init()带锁线程安全的因此如果在一个类的clinit()方法中有耗时很长的操作就可能造成多个线程阻塞引发死锁。并且这种死锁是很难发现的因为看起来它们并没有可用的锁信息。如果之前的线程成功加载了类则等在队列中的线程就没有机会再执行clinit()方法了。那么当需要使用这个类时虚拟机会直接返回给它已经准备好的信息。
4类初始化的主动使用与被动使用
1主动使用
Class只有在必须要首次使用的时候才会被装载Java虚拟机不会无条件地装载Class类型。Java虚拟机规定一个类或接口在初次使用前必须要进行初始化。这里指的“使用”是指主动使用主动使用只有下列几种情况:(即:如果出现如下的情况则会对类进行初始化操作。
当创建一个类的实例时比如使用new关键字或者通过反射、克隆、反序列化。当调用类的静态方法时即当使用了字节码invokestatic指令。当使用类、接口的静态字段时(fina1修饰特殊考虑)比如使用getstatic或者putstatic指令。(对应访问变量赋值变量操作)当使用java.lang.reflect包中的方法反射类的方法时。比如:class.forName(“com.csdn.java.Test”)当初始化子类时如果发现其父类还没有进行过初始化则需要先触发其父类的初始化。如果一个接口定义了default方法那么直接实现或者间接实现该接口的类的初始化该接口要在其之前被初始化。当虚拟机启动时用户需要指定一个要执行的主类(包含main()方法的那个类)虚拟机会先初始化这个主类 针对5补充说明 当Java虚拟机初始化一个类时要求它的所有父类都已经被初始化但是这条规则并不适用于接口。在初始化一个类时并不会先初始化它所实现的接口在初始化一个接口时并不会先初始化它的父接口。因此一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态字段时才会导致该接口的初始化。 针对7补充说明 JVM启动的时候通过引导类加载器加载一个初始类。这个类在调用public static void main(string[])方法之前被链接和初始化。这个方法的执行将依次导致所需的类的加载链接和初始化。
2被动使用
除了以上的情况属于主动使用其他的情况均属于被动使用。被动使用不会引起类的初始化。
并不是在代码中出现的类就一定会被加载或者初始化。如果不符合主动使用的条件类就不会初始化。
当访问一个静态字段时只有真正声明这个字段的类才会被初始化。当通过子类引用父类的静态变量不会导致子类初始化通过数组定义类引用不会触发此类的初始化引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了 4、类的使用
怎么使用Java开发中使用。 5、类的卸载
一个已经加载的类型被卸载的几率很小至少被卸载的时间是不确定的。因此卸载这块看两眼就行了吧
1类、类的加载器、类的实例之间的引用关系
在类加载器的内部实现中用一个Java集合来存放所加载类的引用。另一方面一个Class对象总是会引用它的类加载器调用Class对象的getClassLoader()方法就能获得它的类加载器。由此可见代表某个类的Class实例与其类的加载器之间为双向关联关系。一个类的实例总是引用代表这个类的Class对象。在Object类中定义了getClass()方法这个方法返回代表对象所属类的Class对象的引用。此外所有的Java类都有一个静态属性Class它引用代表这个类的Class对象。
2类的生命周期
当Sample类被加载、链接和初始化后它的生命周期就开始了。当代表Sample类的Class对象不再被引用即不可触及时Class对象就会结束生命周期Sample类在方法区内的数据也会被卸载从而结束Sample类的生命周期。 一个类何时结束生命周期取决于代表它的Class对象何时结束生命周期。