类加载机制与实例化

一个class文件只有被jvm加载到内存中才能被jvm使用,class文件被加载到内存形成可以被jvm使用的java类型的这一过程就成为类加载过程。类的初始化是类加载过程的一个步骤,本质就是按照开发者的意图为类变量赋值。类的实例化是类完全加入到内存以后创建对象的过程。

类加载机制

一、类加载时机

  1. 类加载的时机jvm没有明确说明
  2. 类初始化的时机(有且只有五种):
  • 使用new、getstatic、putstatic、invokestatic四个指令
  • 使用java.lang.reflect包对类进行反射调用时,如果类没有初始化则先对类进行初始化
  • 当初始化类其父类还没初始化时,先对父类进行初始化
  • 虚拟机启动时,会先对执行的主类(有main函数的那个类)进行初始化
  • 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化

以上五类的行为被称为对一个类主动引用,其他均为被动应用,被动引用不会触发类的初始化

二、类加载过程

  1. 加载
  2. 验证
  3. 准备——为变量分配内存并设置变量初始值
  • 分配的类变量不包括实例变量
  • 初始值通常情况下是数据类型的零值
  1. 解析
  2. 初始化——执行类构造方法<clinit>()
  • <clinit>()方法由类变量的赋值定义语句及静态代码块合并而成
  • 同一个类加载器中,类初始化只能被执行一次,即<clinit>()方法只能被执行一次
  • 在执行子类的clinit方法前一定会先执行父类的clinit方法
  • <clinit>()方法语句顺序是有在源码中的静态语句或代码块顺序确定的,定义在前面的代码块只能赋值定义后面的类变量,不能访问后面的类变量.
  • 虚拟机会保证一个类的类构造器<clinit>()在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的类构造器<clinit>(),其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。特别需要注意的是,在这种情形下,其他线程虽然会被阻塞,但如果执行<clinit>()方法的那条线程退出后,其他线程在唤醒之后不会再次进入/执行<clinit>()方法,因为 在同一个类加载器下,一个类型只会被初始化一次。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个线程阻塞,在实际应用中这种阻塞往往是隐藏的。

类的实例化

一、对象创建过程

  1. 根据new指令后面的参数,在常量池中定位到一个类的符号引用,检查这个符号引用代表的是否已经加载,如果没有加载先执行类的加载过程
  2. 为对象分配内存
    1. 指针碰撞
    2. 空闲列表
  3. 对象分配的空间设置空值
  4. 对象头信息设置
  5. 对象的初始化——执行<init>()方法
    1. <init>()方法由实例变量、实例代码块和构造函数三部分组成
    2. 实例变量和实例代码块的顺序在构造函数前面,实例变量和代码块直接的顺序由在源码中的顺序决定
    3. 在执行子类的构造函数之前必然先会执行父类的构造函数,如果我们没有在子类中调用父类的构造函数,编译器自动帮我们生成一个对超类构造函数的调用
    4. 实例化一个类的构造过程是一个递归过程

二、实例化过程的顺序

父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数

三、<clinit>() 与 <init>()区别

  • 类构造器<clinit>()与实例构造器<init>()不同,它不需要程序员进行显式调用,虚拟机会保证在子类类构造器<clinit>()执行之前,父类的类构造<clinit>()执行完毕
  • 在一个类的生命周期中,类构造器<clinit>()最多会被虚拟机调用一次,而实例构造器<init>()则会被虚拟机调用多次,只要程序员还在创建对象

需要注意的是,类的实例化不一定发生在类的初始化完成之后,类初始化的过程中就可能会实例化对象(实际情况下程序员应该尽量避免出现这种情况的,使用为初始化完全的类实例化对象会引起一个意想不到的result......)

question:一个实例变量在对象初始化过程中最多可被赋值几次:

首先在类加载过程准备阶段会被设置零值;

然后如果声明实例变量的同时为其赋值,第二次;

然后在实例代码块中被赋值,第三次;

最后如果在构造函数中被赋值,为第四次,因此在实例化过程中最多会被赋值四次。

ps 面试中的许多这类问题,本质上就是在考察类的加载过程和实例化过程,弄清其原理机制,这类问题就不在话下..........

猜你喜欢

转载自blog.csdn.net/asde1239/article/details/108470866