类生命周期:
- 加载
- 验证
- 准备
- 解析
- 初始化
- 使用
- 卸载
有且只有五种情况立即对类进行初始化:
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类没有进行过初始化,则立即对类进行“初始化”。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类。
- 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果 REF_getStatic、REF_putSatatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要触发其初始化。
加载阶段:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证阶段:
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备阶段:
为了类中静态变量分配内存并设置初始值(数据类型的零值)。
(例如:public static int value=123; 在准备阶段初始值为0而不是123,把value赋值为123是在类初始化阶段过后完成的)
解析阶段:
将常量池内的符号引用替换为直接引用的过程。
初始化阶段:
初始化阶段是执行类构造器<client>()方法的过程。
- <client>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并而成。
- <client>()方法与实例构造器<init>()方法不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的<clinit>;()方法执行之前,父类的<clinit>;()方法已经执行完毕。
- 如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<client>方法。
- 执行接口中的<clinit>()方法不需要先执行父接口的<clinit>()方法。
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁同步,如果多个线程去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法。
类加载器
类相等的条件(Class对象的equals()方法,isAssignableFrom()方法,isInstance()方法):
两个类来源于同一个class文件,被同一个类加载器加载。
类加载器:
- 启动类加载器(Bootstrap ClassLoader)
C++实现,是虚拟机自身的一部分。加载<JAVA_HOME>\lib目录中的Class文件,无法被java程序直接引用。 - 扩展类加载器(Extension ClassLoader)
java代码实现,负责加载<JAVA_HOME>\lib\ext目录中的Class文件。程序可以直接使用。 - 应用程序类加载器(Application ClassLoader)
java代码实现,负责加载classpath上所指定的类库,开发者可以直接使用这个类加载器。一般情况下程序中默认的加载器。
双亲委派模型
工作过程:
如果一个类的加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己去加载。
委托机制的意义 — 防止内存中出现多份同样的字节码。