IT小栈

  • 主页
  • Java基础
  • RocketMQ
  • Kafka
  • Redis
  • Shiro
  • Spring
  • Spring Boot
  • Spring Cloud
  • 资料链接
  • 关于
所有文章 友链

IT小栈

  • 主页
  • Java基础
  • RocketMQ
  • Kafka
  • Redis
  • Shiro
  • Spring
  • Spring Boot
  • Spring Cloud
  • 资料链接
  • 关于

Java类加载机制(二)

2020-03-07

在《Java类加载机制》一文中重点介绍了类加载的一些概念,本节我们将用例子来验证其真实性。

1、类初始化时机

只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下8种:

  1. 创建类的实例,也就是new的方式
  2. 访问某个类或接口的静态变量,或者对该静态变量赋值
  3. 调用类的静态方法
  4. 反射(如Class.forName(“com.shengsiyuan.Test”))
  5. 初始化某个类的子类,则其父类也会被初始化
  6. Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类
  7. 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpececial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
  8. 当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

2、示例分析

例子1:静态变量在类加载阶段的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test {
public static void main(String[] args) {
//对于静态字段来说,只有直接定义了该字段的类才会被初始化
//Child.str1虽然是子类的引用但是Child没有直接定义str1,
System.out.println(Child.str1);
//Parent static block!
//hello world!
//System.out.println(Child.str2);
//Parent static block!
//Child static block!
//hello world!
}
}

class Parent {
public static String str1 = "hello world!";

static {
System.out.println("Parent static block!");
}
}

class Child extends Parent{
public static String str2 = "hello world!";

static {
System.out.println("Child static block!");
}
}

追踪JVM启动时类的加载过程使用JVM参数

-XX:+TraceClassLoading:用于追踪类的加载信息并打印出来

JVM参数格式说明:

-XX:+<option> 表示开启option选项

-XX:-<option> 表示关闭option选项

-XX:<option>=<value> 表示将option设置为value

结论:对于静态字段来说,只有直接定义了该字段的类才会被初始化,子类不是直接定义不会初始化

例子2:确定的常量在类加载阶段的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test2 {
public static void main(String[] args) {
//常量在编译阶段会存入到调用这个常量的方法所在类的常量池中
//本质上调用类并没有直接引用到定义常量的类,因此并不会触发定义常量类的初始化
//注意:这里指的是将常量存放到Test2的常量池中,之后Test2和Parent2就没有关系了
System.out.println(Parent2.str1);
//hello world! 并没有打印静态代码块中的内容
}
}

class Parent2 {
//注意是final常量类
public static final String str1 = "hello world!";
public static final short s = 3;
public static final int i = 0;
public static final int m = 6;
static {
System.out.println("Parent static block!");
}
}

常量在编译阶段会存入到调用这个常量的方法所在类的常量池中,并不会触发定义常量类的初始化。

查看Test2 反编译的结果:

助记符:

ldc 表示将int,float或者String的常量值从常量池推送至栈顶

bipush表示将单字节(-128 ~ 127)的常量值推送至栈顶

sipush表示将一个短整型常量值(-32768 ~ 32767)推送至栈顶

iconst_1表示将int类型为1的推送至栈顶(iconst_0 ~ iconst_5),int常用的0~5单独设置了助记符,还有一个-1是iconst_m1

结论:常量在编译阶段会存入到调用这个常量的方法所在类的常量池中,本质上调用类并没有直接引用到定义常量的类,因此并不会触发定义常量类的初始化

例子3:不确定的常量在类加载阶段的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//当一个常量值在编译期间无法确定,那么其值就不会放入到调用类的常量池中
//这时在程序运行时,就会导致主动使用这个常量所在的类,显然会导致这个类的初始化
public class Test3 {
public static void main(String[] args) {
System.out.println(Parent3.str1);
//Parent3 static block!
//2b5ed621-9e3e-448b-a45a-257eb19e850e
}
}

class Parent3 {
//编译期不知道str1的具体值,只能在运行期才能确定
public static final String str1 = UUID.randomUUID().toString();

static {
System.out.println("Parent3 static block!");
}
}

结论:当一个常量值在编译期间无法确定,那么其值就不会放入到调用类的常量池中,这时在程序运行时,就会导致主动使用这个常量所在的类,显然会导致这个类的初始化

例子4:数组在类加载阶段的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
对于数组实例来说,其类型是由JVM在运行期动态生成的,表示为[Lcn.com.mingxungu.classloader.Parent4
这种形式,动态生成的类型,其父类就是Object

对于数组来说,JavaDoc经常将构成数组的元素为Component,实际上就是将数组降低一个维度的类型
助记符:
anewarray:表示创建一个引用类型(如类、接口、数组)数组,并将其引用值压入栈顶
newarray:表示创建一个指定额原始类型(如int、float、char等)的数组,并将其引用值压入栈顶
*/

public class Test4 {
public static void main(String[] args) {
//Parent4静态代码块不会被打印
Parent4[] parent4s = new Parent4[1];
System.out.println(parent4s.getClass());

Parent4[][] parent4s1 = new Parent4[1][1];
System.out.println(parent4s1.getClass());

int[] ints = new int[1];
System.out.println(ints.getClass());
}
}

class Parent4 {

static {
System.out.println("Parent4 static block!");
}
}

结论:对于数组实例来说,其类型是由JVM在运行期动态生成的,表示为[Lcn.com.mingxungu.classloader.Parent4这种形式,动态生成的类型,其父类就是Object

例子5:接口在类加载阶段的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
当初始化一个类时,并不会先初始化它所实现的接口
在初始化一个接口时,并不会先初始化它的父接口
*/
public class Test5 {
public static void main(String[] args) {
System.out.print(Child5.b);
//5
}
}

interface Parent5 {
public static Thread thread = new Thread(){
//代码块,会在每次创建实例时执行,在构造方法执行之前执行
{
System.out.println("Parent5 Invoked!");
}
};
}

class Child5 implements Parent5 {
public static int b = 5;
}

结论:当初始化一个类时,并不会先初始化它所实现的接口
在初始化一个接口时,并不会先初始化它的父接口

例子6:ClassLoader类的loadClass的方法加载一个类的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test8 {
public static void main(String[] args) throws Exception {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> clazz = classLoader.loadClass("cn.com.mingxungu.classloader.CL");
System.out.println(clazz);
System.out.println("============");
//反射是对类的主动使用,会触发类的初始化
Class<?> clazzs = Class.forName("cn.com.mingxungu.classloader.CL");
System.out.println(clazzs);
}
}

class CL {
static {
System.out.println("Class CL!");
}
}

结论:调用classLoader类的loadClass的方法加载一个类,并不是对类的主动使用,不会导致类的初始化

反射是对类的主动使用,会触发类的初始化

本文作者: 顾 明 训
本文链接: https://www.itzones.cn/2020/03/07/Java类加载机制-二/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!
  • jvm
  • java

扫一扫,分享到微信

微信分享二维码
Kafka生产者
Kafka主题管理
  1. 1. 1、类初始化时机
  2. 2. 2、示例分析
    1. 2.1. 例子1:静态变量在类加载阶段的情况
    2. 2.2. 例子2:确定的常量在类加载阶段的情况
    3. 2.3. 例子3:不确定的常量在类加载阶段的情况
    4. 2.4. 例子4:数组在类加载阶段的情况
    5. 2.5. 例子5:接口在类加载阶段的情况
    6. 2.6. 例子6:ClassLoader类的loadClass的方法加载一个类的情况
© 2020 IT小栈
载入天数...载入时分秒... || 本站总访问量次 || 本站访客数人次
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链

tag:

  • jvm
  • Java基础
  • kafka HW
  • kafka Leader Epoch
  • kafka
  • kafka位移主题
  • kafka位移提交
  • kafka副本机制
  • kafka ISR
  • zookeeper
  • kafka消息丢失
  • kafka日志存储
  • kafka Log Clean
  • kafka Log Compaction
  • kafka消费位移设置
  • kafka Rebalance
  • kafka分区算法
  • kafka生产者拦截器
  • kafka SASL/SCRAM
  • kafka ACL
  • redis
  • redis Ziplist
  • redis Hashtable
  • redis LinkedList
  • redis QuickList
  • redis intset
  • redis String
  • redis SDS
  • redis SkipList
  • redisDb
  • redisServer
  • redis 简介
  • Redis Cluster
  • 主从同步
  • RocketMQ高可用HA
  • 事务消息
  • 内存映射
  • MMAP
  • 同步刷盘
  • 异步刷盘
  • 消息存储文件
  • RocketMQ安装
  • 延迟消息
  • RocketMQ入门
  • 推拉模式
  • PushConsumer
  • 消费结果处理
  • rebalance
  • RocketMQ权限控制
  • RocketMQ ACL
  • 消息过滤
  • 消息重试
  • 消费位置
  • 集群消费
  • 广播消费
  • 运维命令
  • shiro源码分析
  • shiro入门
  • IOC和DI
  • Spring创建Bean
  • Bean生命周期
  • Sping属性注入
  • 异常
  • SpringMVC
  • springCloud
  • Eureka

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 我的OSCHINA
  • 我的CSDN
  • 我的GITHUB
  • 一生太水