Java杂谈3——类加载机制与初始化顺序
Java语言的哲学:一切都是对象。对于Java虚拟机而言,一个普通的Java类同样是一个对象,那如果是对象,必然有它的初始化过程。一个类在JVM中被实例化成一个对象,需要经历三个过程:加载、链接和初始化。
JAVA类的加载
加载:从字节码二进制文件——.class文件将类加载到内存,从而达到类的从硬盘上到内存上的一个迁移,所有的程序必须加载到内存才能工作。一个Java类在被加载到内存后会在Java堆中创建一个类(java.lang.Class)对象,同时JVM为每个类对象都维护一个常量池(类似于符号表)。
类加载器的分类
Java类都是由类加载器进行加载,从大的分类来看,Java提供两种类型的类加载器:和用户自定义的类加载器。Java默认提供了3个类加载器,分别是:Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。
- Bootstrap ClassLoader:这个加载器不是一个Java类,而是由底层的c++实现,负责在虚拟机启动时加载Jdk核心类库以及加载后两个类加载器。
- Extension ClassLoader:是一个普通的Java类,继承自ClassLoader类,负责加载{JAVA_HOME}/jre/lib/ext/目录下的所有jar包。
- App ClassLoader:是Extension ClassLoader的子对象,负责加载应用程序classpath目录下的所有jar和class文件。
除了以上3个类加载其之外,用户还可以继承ClassLoader类来自定义相应的类加载器。JVM通过一种双亲委托模型来避免重复加载同一个类,在这种模型中,当一个类C需要被某一个类加载器L加载时,会优先在类加载器L的父类中查找类C是否已经被加载。下面的代码是具体的双亲委托模式的实现:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
} if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); // this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
所有的Java类加载器都保证是引导类加载器的孩子,具体的ClassLoader体系结构见下图:
链接:包含了验证和准备类或者接口、包括了它的直接父类、直接父接口、元素类型以及一些必要的操作。Java虚拟机规范并没明确要求被准备的类或接口需要被解析,只需要验证相关的类或接口的字节码符合JVM规范。
类的初始化:执行类的static块和初始化类内部的静态属性。
Class.forName()与ClassLoader.loadClass()
通常用这两种方式来动态加载一个java类,但是两个方法之间也是有一些细微的差别。
Class.forName方式
查看Class类的具体实现可知,实质上这个方法是调用原生的方法:
private static native Class<?> forName0(String name, boolean initialize,ClassLoader loader);
形式上类似于Class.forName(name,true,currentLoader);
综上所述,Class.forName如果调用成功
- 保证一个Java类被有效得加载到内存中;
- 类默认会被初始化,即执行内部的静态块代码以及保证静态属性被初始化;
- 默认会使用当前的类加载器来加载对应的类。
ClassLoader.loadClass方式
如果采用这种方式的类加载策略,由于双亲托管模型的存在,最终都会将类的加载任务交付给Bootstrap ClassLoader进行加载。跟踪源代码,最终会调用原生方法:
private native Class<?> findBootstrapClass(String name);
与此同时,与上一种方式的最本质的不同是,类不会被初始化。
总结ClassLoader.loadClass如果调用成功:
- 类会被加载到内存中;
- 类不会被初始化,只有在之后被第一次调用时类才会被初始化;
- 之所以采用这种方式的类加载,是提供一种灵活度,可以根据自身的需求继承ClassLoader类实现一个自定义的类加载器实现类的加载。(很多开源Web项目中都有这种情况,比如tomcat,struct2,jboss。原因是根据Java Servlet规范的要求,既要Web应用自己的类的优先级要高于Web容器提供的类,但同时又要保证Java的核心类不被任意覆盖,此时重写一个类加载器就很必要了)
类初始化顺序
对于普通的Java程序,一般都不需要显式的声明来动态加载Java类,只需要用import关键字将相关联的类引入,类被第一次调用的时候,就会被加载初始化。那对于一个类对象,其内部各组成部分的初始化顺序又是如何的呢?
一个Java类对象在初始化的时候必定是按照一定顺序初始化其静态块、静态属性、类内部属性、构造方法。这里我们讨论的初始化分别针对两个对象,一个是类本身还有一个是类实例化的对象。
类本身的初始化会在类被加载完毕、链接完成之后,由Java虚拟机负责调用<clinit>方法完成。在这个方法中依次完成了堆类内部静态块的调用和类内部静态属性的初始化(如果存在父类,父类会优先进行初始化)。不论创建多少个实例化的对象,一个类只会被初始化一次。
类实例化的对象通过new操作创建,Java虚拟机保证一个类在new操作实例化其对象之前已经完成了类的加载、链接和初始化。之后Java虚拟机会调用<init>方法完成类实例化对象的初始化。这个方法会优先按照代码中顺序完成对类内部个属性的初始化,之后再调用类的构造函数(如果有父类,则优先调用父类的构造函数)。
PS:需要注意的是上述提到的<init>和<clinit>方法都是非法的Java方法名,是由编译器命名的,并不能由编码实现。
综上所述,我们大致可以得出以下结论,对于一个类,在实例化一个这个类的对象时,我们可以保证以下这样的优先级进行初始化:
类内部静态块 > 类静态属性 > 类内部属性 > 类构造函数
再讨论加载顺序
最近看了几篇谈设计模型单例模式的Java实现的文章,在实现一个具体的线程安全单例Java类中,一个简单且被推荐的方式使用内部静态类存储一个静态属性,这就涉及到内部静态类的初始化顺序的问题,结合想到这篇文章中也没有讨论过这个问题,继而做了一些实验,代码如下:
public class Test { public static class Inner{ public final static Test testInstance = new Test(3); static {
System.out.println("TestInner Static!");
}
} public static Test getInstance(){
return Inner.testInstance;
} public Test(int i ) {
System.out.println("Test " + i +" Construct! ");
} static {
System.out.println("Test Stataic");
} public static Test testOut = new Test(1); public static void main(String args[]){
Test t = new Test(2);
Test.getInstance();
} }
实验的结果证明顺序如下:
内部类静态属性(或静态块)会在内部类第一次被调用的时候按顺序被初始化(或执行);而类内部静态块的执行先于类内部静态属性的初始化,会发生在类被第一次加载初始化的时候;类内部属性的初始化先于构造函数会发生在一个类的对象被实例化的时候。
综合上面的结论,上面这段代码的结果是什么呢?问题就留给读者们自行思考吧。
Java杂谈3——类加载机制与初始化顺序的更多相关文章
- Java基础:类加载机制
之前的<java基础:内存模型>当中,我们大体了解了在java当中,不同类型的信息,都存放于java当中哪个部位当中,那么有了对于堆.栈.方法区.的基本理解以后,今天我们来好好剖析一下,j ...
- java 虚拟机的类加载机制
Java 虚拟机的类加载机制 关于类加载机制: 虚拟机把描述类的数据从Class 文件加载到内存,并对数据进行效验.转换解析和初始化,最终 形成可以被虚拟机直接使用的Java 类型,就是虚拟机的类 ...
- Java语法专题2: 类变量的初始化顺序
合集目录 Java语法专题2: 类变量的初始化顺序 问题 这也是Java面试中出镜率很高的基础概念问题 描述一下多级继承中字段初始化顺序 描述一下多级继承中类变量初始化顺序 写出运行以下代码时的控制台 ...
- 【转】两道面试题,带你解析Java类加载机制(类初始化方法 和 对象初始化方法)
本文转自 https://www.cnblogs.com/chanshuyi/p/the_java_class_load_mechamism.html 关键语句 我们只知道有一个构造方法,但实际上Ja ...
- 深入理解Java虚拟机-----------虚拟机类加载机制
虚拟机类加载机制 类从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括:加载,验证,准备,解析,初始化,使用,卸载等7个阶段.其中,验证,准备,解析3个部分称为连接. 以上7个阶段中,加载, ...
- Java虚拟机:类加载机制详解
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 大家知道,我们的Java程序被编译器编译成class文件,在class文件中描述的各种信息,最终都需要加载到虚拟机内存才能运行和使用,那么 ...
- java虚拟机的类加载机制
引言 我们写的代码是放在.java文件中,经过编译器编译后,转成.class文件.Class文件是一串二进制流,它可以被各平台的虚拟机所接受,实现跨平台. 虚拟机将描述类的数据从class文 ...
- 深入理解java虚拟机(三)-----类加载机制
类加载机制jvm把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被jvm直接使用的java类型.在java中,类型的加载.连接和初始化都是在程序运行期间完成的 ...
- Java虚拟机--虚拟机类加载机制
虚拟机类加载机制 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 类的生命周期如下: 加载 ...
随机推荐
- [CVPR2017]Online Video Object Segmentation via Convolutional Trident Network
基于三端卷积网络的在线视频目标分割 针对半监督视频目标分割任务,作者采取了和MaskTrace类似的思路,以optical flow为主. 本文亮点在于: 1. 使用共享backbone,三输出的自编 ...
- webpack 的第三方库分离并持久化缓存
我们常常需要在浏览器缓存一些稳定的资源,如第三方库等.要达到这个目标,只需要两步: 1.提取出“稳定的资源”: 2.提供稳定的文件hash . 处理后的出的文件就像这样子: app.1w3ad4q4. ...
- MyBatis查询结果resultType返回值类型详细介绍
一.返回一般数据类型 比如要根据 id 属性获得数据库中的某个字段值. mapper 接口: // 根据 id 获得数据库中的 username 字段的值 String getEmpNameById( ...
- NGINX: 反向代理 websocket
参考: [ Using multiple nodes ] [ Nginx 官网 WebSocket proxying ] 关于 websocket 的介绍可以看阮大大的这篇 [ WebSocket 教 ...
- 【洛谷 P2485】 [SDOI2011]计算器 (BSGS)
题目链接 第一问:快速幂 第二问:扩欧解线性同余方程 第三问:\(BSGS\) 三个模板 #include <cstdio> #include <cmath> #include ...
- busybox syslog介绍
busybox中提供了一个syslog. 配置日志处理规则 可通过设置/etc/syslog.conf具体配置不同log的处理规则,以下的简单配置,将log全部写到/var/log/messages和 ...
- Appium===Appium+Python API(转)
Appium+python自动化8-Appium Python API 前言: Appium Python API全集,不知道哪个大神整理的,这里贴出来分享给大家. 1.contexts contex ...
- Bean装配之@Autowired注解
@Required(不常用) @Autowired(常用) 下面用例子解释以上内容: @Autowired注解的三种方式如下,第一种是直接在属性名上加注解,这样就可以不用在写set方法进行注入,这种方 ...
- Asp.Net Core 项目实战参考
Asp.Net Core 项目实战链接 http://www.cnblogs.com/fonour/p/5904530.html
- svn突然不能用了!
case:周五下班将电脑关机,带回家本来打算周末加班的:但是后来周末有事,没有加班,周六和周末电脑根本没有开机.本周一过来开机,打开eclipse准备更新代码的时候,突然发现与资源库同步操作的时候报错 ...