java 类的加载
java程序的三个不同阶段,如有错请留言,谢谢!
如上图,类加载就框1至框2中间的椭圆部分过程,这是本篇文章要介绍的主题,
1 静态加载:编译时就加载的类,(如找不到此类,会造成程序无法运行)
2 动态加载:运行时才会加载的类, 如程序运行时不用该类,则此类一直都不会加载, 此类不存在也没影响, 如 反射机制。
类加载的时机
1 创建对象时,即new一个对象时,
2 读取或设置一个类的静态字段以及调用一个类的静态方法。(被 final修饰的静态字段除外、因为其已在编译期把结果放入常量池了),如示例1
3 当初始化类的父类还没有进行过初始化,则需要先触发其父类的初始化。(而一个接口在初始化时,并不要求其父接口全部都完成了初始化)
4 通过反射 //动态加载
示例1
public class ClassJiaZaiShunXunMain { public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(ClassJiaZaiShunXun.Shoujiodm); } } public class ClassJiaZaiShunXun { public final static int Shoujiodm = 90; static { System.out.println("ClassJiaZaiShunXunA jingtaidamakuai "); } public static void mthondOdm() { System.out.println("ClassJiaZaiShunXunA::mthondOdm"); } } 情况1 public final static int Shoujiodm = 90; //有final修饰时,程序的执行结果是: 90 情况2 public static int Shoujiodm = 90; //没有final修饰时,程序的执行结果是: ClassJiaZaiShunXunA jingtaidamakuai 90 //也就是说情况1状态下,ClassJiaZaiShunXun文件是没有被虚拟机加载初始化, 如果有加载初始化的话必定会同时初始化其下面的静态代码块
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。如下图
类加载的流程图
加载
加载是根据特定的名称查找类或者是接口类型的二进制形式来创建类或接口的所对应的class对象过程
通过全限定类名来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
链接:
(1)验证:确保.CLass文件字节流中包含的信息符合当前虚拟机的要求。不会危害的自身安全。Eg:这个类的父类是否继承了不允许被继承的类(被finaI修饰的类)
(2)准备:准备阶段正式为类变量(静态变量)分配内存以及为类变量设置初始值。注意:这时分配内存的仅仅包括类变量(被static修饰的变量)不包括实例变量,实例变量是在对象实例初始化时分配内存,不是在准备阶段分配内存。这里所说的初始值是指数据类型的基本数据类型的初始值,如 int类型变量是0,Long类型变量是0L,boolean类型是false.真正的用户赋值初始化需要在后面的初始化阶段中。
例如
class ShoujiOdmCom{
static int shoujiodm = 99;
}
准备阶段会为shoujiodm静态变量分配4个字节的空间,并初始化值为0。
(3)解析:将方法区中的字符引用转换成直接引用,即我们在调用方法时都是通过方法名(字符引用)来引用,字符引用转换成直接引用就是直接引用方法所对应的内存地址。
初始化:
执行实际的类初始化动作赋值语句,静态代码块。到此初始化阶段才真正开始执行类中定义的 Java 程序代码,此阶段是执行 <clinit>()
方法的过程。
对静态的变量(static)进行赋值及静态代码块。(静态方法不包括哦,方法是需要调用不会自动跑进去的)
对静态变量的初始化顺序是以在类中定义的顺序(.java文件中由上至下出现的顺序),
静态方法,调用的时候才会加载,不调用的时候不会加载
例如
class ShoujiOdmCom{
static int shoujiodm = 99;
}
初始化阶段会为shoujiodm的值可以赋值为99了。
一个类要实例化,必须先要让自己初始化,类初始化过程主要是对静态成员的初始化。
<clinit>()方法详解
当一类编译之后,字节码文件中产生一个类构造器方法:<clinit>(),此方法中的逻辑就是当前类中的静态变量和静态代码块的初始化逻辑整合。
此方法会按语句在源文件中出现的顺序,依次自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。(不包括构造器中的语句。构造器是初始化对象的,类加载完成后,创建对象时候将调用的 <init>() 方法来初始化对象)
<clinit>() 不需要显式调用父类(接口除外,接口不需要调用父接口的初始化方法,只有使用到父接口中的静态变量时才需要调用)的初始化方法 <clinit>(),虚拟机会保证在子类的 <clinit>() 方法执行之前,父类的 <clinit>() 方法已经执行完毕,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
<clinit>() 方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成 <clinit>() 方法。
虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>() 方法完毕。
---------------------------------------------------------------------------------------------------------------
下一篇:Java实例化对象的顺序
评论