类加载的时机

  类加载的生命周期为: 加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个阶段统称为连接。其中加载与连接时交叉执行的。

类必须初始化的六种情况

  1. 遇到new、getstatic、putstatic、或者invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需先触发其初始化阶段。
  2. 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需先触发其初始化阶段。
  3. 当初始化类的时候,其父类还没有初始化,则需先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
  5. 当使用JDK7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需先触发其初始化。
  6. 当一个接口中定义了JDK8新加入的默认方法(default关键字修饰的接口方法)时,如果有这个接口的实现类进行初始化的场景,那该接口要在其之前被初始化。

注意: ① 《Java虚拟机规范》中使用了非常强烈的限定语——"有且仅有"这六种情况。② 接口与类真正区别的是第三种初始化场景: 接口在初始化时,并不要求其父接口全部完成初始化,只有真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。

被动引用示例

示例一

package com.chinda.init;

/**
* 被动使用类字段演示一
* 通过字类引用父类的静态字段, 不会导致子类初始化
*
* @author Wang Chinda
* @date 2020/3/16
* @see
* @since 1.0
*/
public class SuperClass { static {
System.out.println("SuperClass init!");
} public static int value = 123;
}
package com.chinda.init;

/**
* @author Wang Chinda
* @date 2020/3/16
* @see
* @since 1.0
*/
public class SubClass extends SuperClass { static {
System.out.println("SubClass init!");
}
}
    /**
* 控制台打印输出
* SuperClass init!
* 123
* <p>
* 通过字类引用父类的静态字段, 不会导致子类初始化
*/
@Test
public void test1() {
System.out.println(SubClass.value);
}

示例二

    /**
* 通过数组定义来引用类, 不会触发此类的初始化
*/
@Test
public void test2() {
SubClass[] sca = new SubClass[10];
}

示例三

package com.chinda.init;

/**
* @author Wang Chinda
* @date 2020/3/16
* @see
* @since 1.0
*/
public class ConstClass { static {
System.out.println("ConstClass init");
} public static final String HELLOWORLD = "helloworld";
}
    /**
* 常量在编译阶段会存入调用类的常量池中, 本质上没有直接引用到定义常量的类, 因此不会触发定义常量的类的初始化。
*/
@Test
public void test3() {
System.out.println(ConstClass.HELLOWORLD); }

类加载的过程

加载

加载阶段时整个类加载过程中的一个阶段,在加载阶段,Java虚拟机需要完成三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据机构。
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

注意:① 数组不是通过类加载器创建的,是Java虚拟机直接在内存中动态构造出来的。但是数组的元素类型是需要类加载器来完成加载。② 加载阶段与连接阶段的部分动作是交叉进行的。

验证

这一阶段的目的就是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证虚拟机的自身安全。验证阶段大致会完成四个阶段的校验动作:

  1. 文件格式校验。
  2. 元数据校验。
  3. 字节码验证。
  4. 符号引用验证。

准备

准备阶段是正式为类中定义的变量(即静态变量)分配内存并设置类变量初始值的阶段。这些变量所使用的内存都在方法区进行分配。

public static int value = 123;

变量value在准备阶段过后的初始值为0而不是123,因为这时尚未开始执行任何Java方法,而把value赋值为123的putstatic执行时程序被编译后,存放于类构造器()方法中,所以把value赋值为123的动作要到类初始化阶段才会被执行。

public static final int value = 123;

编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue的设置将value赋值为123。

注意:方法区本身是一个逻辑上的区域,JDK7及以前,HotSpot使用永久代来实现方法区时,实现时完全符合这种逻辑概念的。JDK8及以后,类变量则随着Class类对象一起存放在Java堆中,这时候"类变量在方法区"就完全是一种对逻辑概念的表述。

解析

  1. 类或接口的解析。
  2. 字段解析。
  3. 方法解析。
  4. 接口方法解析。

初始化

初始化阶段就是执行类构造器()方法的过程。

  1. ()方法是有编译器自动收集类变量赋值动作和静态语句块(static {}块)种的语句结合产生的,编译器收集的顺序是由语句在源文件中出现的先后顺序决定的。
  2. 子类()方法执行前,父类的()方法必须已经执行完毕。
  3. 接口中也存在()方法,但是不强制父接口的()方法必须在子接口()前。
  4. Java虚拟机必须保证一个类的()方法在多线程种被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行完毕()方法。

示例一

/**
* 非法的向前引用
* 静态语句块种只能访问到定义在静态语句块之前的变量, 定义在它之后的变量, 在前面的静态语句块可以赋值, 但是不可以访问。
* @author Wang Chinda
* @date 2020/3/26
* @see
* @since 1.0
*/
public class InitTest01 {
static {
i = 0; // 可以正常编译通过, 原因是在准备阶段已经将此变量赋值为0
System.out.println(i);// 这句编译器会提示"非法的向前引用"
}
static int i = 1;
}

示例二

/**
* 控制台打印值是:2
* 原因: 因为子类<clinit>()方法在执行前, 父类的<clinit>()方法必须已经执行完毕。
* @author Wang Chinda
* @date 2020/3/26
* @see
* @since 1.0
*/
public class InitTest02 {
static class Parent {
public static int a = 1; static {
a = 2;
}
} static class Sub extends Parent {
public static int b = a;
} public static void main(String[] args) {
System.out.println(Sub.b);
}
}

示例三

/**
* 控制台打印
*
* Thread[Thread-0,5,main] start
* Thread[Thread-1,5,main] start
* Thread[Thread-0,5,main] init DeadLoopClass
* 分析: Thread-0 初始化执行DeadLoopClass类的<clinit>()方法时, 将类加锁, 导致Thread-1一直被阻塞状态。
*
* @author Wang Chinda
* @date 2020/3/26
* @see
* @since 1.0
*/
public class InitTest03 { static class DeadLoopClass {
static {
// 如果不加上这个if语句, 编译器将提示"Initializer must be able to complete normally", 拒绝编译
if (true) { System.out.println(Thread.currentThread() + " init DeadLoopClass");
while (true) {
}
}
}
} public static void main(String[] args) {
Runnable script = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread() + " start");
DeadLoopClass dlc = new DeadLoopClass();
System.out.println(Thread.currentThread() + " run over");
}
}; Thread thread1 = new Thread(script);
Thread thread2 = new Thread(script);
thread1.start();
thread2.start();
}
}

类加载器

”通过一个类的全限定名获取描述该类的二进制字节流“这个动作被称为“类加载器”(Class Loader)。分别为:启动类加载器(BootstrapClassLoader)、扩展类加载器(ExtensionClassLoader)、应用程序加载器(ApplicationClassLoader)。

类与类加载器

同一个Class文件通过不同的加载器加载,在Java虚拟机种是两个相互独立的类。

示例

package com.chinda.init;

import java.io.IOException;
import java.io.InputStream; /**
* @author Wang Chinda
* @date 2020/3/27
* @see
* @since 1.0
*/
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
}; Object obj = myLoader.loadClass("com.chinda.init.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof ClassLoaderTest);
System.out.println(obj instanceof com.chinda.init.ClassLoaderTest);
}
}

运行结果:

class com.chinda.init.ClassLoaderTest
false
false

双亲委外模型

双亲委派模型的工作流程是:如果一个类加载器收到类加载请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,一直委派到最顶层启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。

通俗讲:

孙子弄到了一个梨,给了他爹,让他爹吃。他爹一想,孙子他爷爷还没吃,就拿给了孙子他爷爷。孙子他爷爷拿到梨看了下和孙子他爹说,这个梨太硬,牙口不好,吃不了,给了孙子他爹。孙子他爹拿到梨,看梨嫌弃梨酸,就把梨又给了这个孙子。最后孙子拿这个梨吃。能吃,说明类加载成功,吃不了,抛出异常。要是孙子他爹不嫌弃酸的话,这个梨就孙子他爹吃了,也就是说,这个类孙子他爹加载。

启动类示例代码

/**
* @author Chinda.Wang
* @date 2020/3/9
* @see
* @since 1.0
*/
public class ClassLoaderTest1 { public static void main(String[] args) {
// 应用加载器(AppClassLoader)
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(appClassLoader);
// 扩展加载器(ExtClassLoader)
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println(extClassLoader);
// 启动类加载器(BootstrapClassLoader)
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);
}
}

运行结果:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@449b2d27
null

启动类加载器是C实现的,所以返回是null

加载路径

/**
* @author Chinda.Wang
* @date 2020/3/9
* @see
* @since 1.0
*/
public class ClassLoaderTest2 { public static void main(String[] args) {
System.out.println("****************启动类加载器***************");
// 启动类加载器(BootstrapClassLoader)加载路径
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
Arrays.stream(urls).forEach(System.out::println); System.out.println("****************扩展加载器***************");
String property = System.getProperty("java.ext.dirs");
Arrays.stream(property.split(";")).forEach(System.out::println);
}
}

运行结果:

****************启动类加载器***************
file:/E:/Java/jdk1.8.0_201/jre/lib/resources.jar
file:/E:/Java/jdk1.8.0_201/jre/lib/rt.jar
file:/E:/Java/jdk1.8.0_201/jre/lib/sunrsasign.jar
file:/E:/Java/jdk1.8.0_201/jre/lib/jsse.jar
file:/E:/Java/jdk1.8.0_201/jre/lib/jce.jar
file:/E:/Java/jdk1.8.0_201/jre/lib/charsets.jar
file:/E:/Java/jdk1.8.0_201/jre/lib/jfr.jar
file:/E:/Java/jdk1.8.0_201/jre/classes
****************扩展加载器***************
E:\Java\jdk1.8.0_201\jre\lib\ext
C:\Windows\Sun\Java\lib\ext

JVM虚拟机(一):类加载机制的更多相关文章

  1. JVM之Java类加载机制

    什么是类加载机制 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这既是虚拟机的类加载机制 类的生命周期 生命周期简述 ...

  2. 深入理解Java虚拟机(类加载机制)

    文章首发于微信公众号:BaronTalk 上一篇文章我们介绍了「类文件结构」,这一篇我们来看看虚拟机是如何加载类的. 我们的源代码经过编译器编译成字节码之后,最终都需要加载到虚拟机之后才能运行.虚拟机 ...

  3. 【进阶之路】深入理解Java虚拟机的类加载机制(长文)

    我们在参加面试的时候,经常被问到一些关于类加载机制的问题,也都会在面试之前准备的时候背好答案,但是我们是否有去深入了解什么是类加载机制呢?这段时间因为一些事情在家看了些书,这次就和大家分享一些关于Ja ...

  4. Java虚拟机:类加载机制详解

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 大家知道,我们的Java程序被编译器编译成class文件,在class文件中描述的各种信息,最终都需要加载到虚拟机内存才能运行和使用,那么 ...

  5. java 虚拟机的类加载机制

    Java 虚拟机的类加载机制 关于类加载机制: ​ 虚拟机把描述类的数据从Class 文件加载到内存,并对数据进行效验.转换解析和初始化,最终 形成可以被虚拟机直接使用的Java 类型,就是虚拟机的类 ...

  6. java虚拟机的类加载机制

    引言 我们写的代码是放在.java文件中,经过编译器编译后,转成.class文件.Class文件是一串二进制流,它可以被各平台的虚拟机所接受,实现跨平台.      虚拟机将描述类的数据从class文 ...

  7. 【Java杂货铺】JVM#虚拟机加载机制

    代码编译的结果从本地机器码变为字节码,是储存格式发展的一小步,却是编程语言发展的一大步--<深入理解Java虚拟机> 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转化 ...

  8. 深入理解Java虚拟机之类加载机制篇

    概述 ​ 虚拟机把描述类的数据从 Class 文件加载到内存中,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,就是虚拟机的类加载机制. ​ 在Java语言里面,类型的 ...

  9. JVM学习笔记:虚拟机的类加载机制

    JVM类加载机制分两部分来总结: (1)类加载过程 (2)类加载器 一.JVM类加载过程 类的加载过程:加载 →连接(验证 → 准备 → 解析)→ 初始化. 类的生命周期:加载 →连接(验证 → 准备 ...

  10. 【JVM】JVM系列之类加载机制(四)

    一.前言 前面分析了class文件具体含义,接着需要将class文件加载到虚拟机中,这个过程是怎样的呢,下面,我们来仔细分析. 二.什么是类加载机制 把class文件加载到内存,并对数据进行校验.转换 ...

随机推荐

  1. PHP+Ajax点击加载更多内容

    css样式: <style type="text/css"> #more{margin:10px auto;width: 560px; border: 1px soli ...

  2. 怎么用MindManager自带的模板和设计画思维导图

    小编知道大家平时工作学习都很忙,思维导图能完成的效率越高越好.所以今天,小编就为大家介绍两个能高效使用思维导图软件完成制作思维导图的小技巧.保证内容充实美观,还不费时间. 一.使用模板 打开MindM ...

  3. H5系列之常用的语义元素

    H5添加了几个新标签,带有语义化的标签,像我们的div 和 span 标签,你说他两能干嘛呢, 好像他两什么事都能干.举个例子,你家里的房子,有几个房间,如果不分房间的话,是不是你 今天睡这里,明天睡 ...

  4. JPA使用之@Query的常用写法

    准备 实体 @Data @Table(name = "task_apply") @Entity public class TaskApply { @Id @GeneratedVal ...

  5. JAVA 中的Optional (臭名昭著的空指针异常(NullPointerException))

    从 Java 8 引入的一个很有趣的特性是 Optional  类.Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) -- 每个 Java 程序员都 ...

  6. Contest 991

    A 先判合法然后容斥. 时间复杂度 \(O\left(1\right)\). B 贪心选最小的实验做成 \(5\) 分. 时间复杂度 \(O\left(n\right)\). 剩下的鸽了.

  7. Java基础教程——List(列表)

    集合概述 Java中的集合,指一系列存储数据的接口和类,可以解决复杂的数据存储问题. 导包:import java.util.*; 简化的集合框架图如下: List·列表 ArrayList List ...

  8. Java集合【6.1】-- Collection接口源码详解

    目录 一.Collection接口简介 二.Collection源码分析 三.Collection的子类以及子类的实现 3.1 List extend Collection 3.2 Set exten ...

  9. java学生简单管理系统

    1 //设一个班有n名学生,期末考试5门,编写程序评定学生奖学金 2 514 //要求打印输出一二等奖学金学生的学号,姓名和各科成绩 3 515 //总成绩超过全班总平均成绩20%一等奖学金,超过总平 ...

  10. VisualStudio 编写汇编代码相关设置

    VS编写汇编代码方法 新建空项目,不创建解决方案 项目右键,Build Customizations,选择masm 新建源文件,后缀为.ASM 编写代码 .386 ; Tells MASM to us ...