虚拟机类加载--2.类的初始化

初始化是类加载过程的最后一步,但由于比较重要,故放在前面先讲。

在前面的连接(准确来说是准备)阶段,类的变量已经被赋予默认值(如int类型为0,布尔类型为false,引用类型为null等)。而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源。或者可以从另一个角度来表达:初始化阶段是执行类构造器<clini>()方法的过程。

1. <clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static{})中的语句合并产生的,收集的顺序是由语句在源文件中出现顺序所决定。静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,可以赋值,但不能访问。如下将报错:

 

2. <clinit>()方法与类的构造函数(或者说实例构造器<init>()方法)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此,在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object;

3.由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作,如下将打印2

复制代码
 1 package com.khlin.initialization;
 2 
 3 public class App {
 4 
 5     public static void main(String[] args) {
 6         System.out.println(Sub.b);
 7     }
 8 
 9     static class Parent {
10         public static int a = 1;
11         static {
12             a = 2;
13         }
14     }
15 
16     static class Sub extends Parent {
17         public static int b = a;
18     }
19 }
复制代码

 

4.<clinit>()方法对于类或接口并不是必需的,如果类没有静态语句块,也没有赋值动作,那么编译器可以不为这个类生成<clinit>()方法;

5.接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,所以接口和类一样都会生成<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,才会初始化。另化,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法;

6.虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。单例的initialization on demand holder模式就是利用了这一特性。如果在一个类的<clinit>()方法中有耗时很长的操作,就有可能造成多个进程阻塞。需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法的那条线程退出<clinit>()之后,其他线程唤醒后不会再次进入<clinit>()方法。同一个类加载器下,一个类型只会初始化一次。

 

复制代码
 1 package com.khlin.initialization;
 2 
 3 public class EndlessLoop {
 4 
 5     static {
 6         if (true) {
 7             System.out.println(Thread.currentThread() + " init EndlessLoop");
 8             while (true) {
 9             }
10         }
11     }
12 
13 }
复制代码
复制代码
 1 public static void main(String[] args) {
 2         Runnable runnable = new Runnable() {
 3 
 4             @Override
 5             public void run() {
 6                 System.out.println(Thread.currentThread() + " is running....");
 7                 EndlessLoop endlessLoop = new EndlessLoop();
 8                 System.out.println(Thread.currentThread() + " runs over");
 9             }
10         };
11 
12         Thread threadA = new Thread(runnable);
13         Thread threadB = new Thread(runnable);
14         threadA.start();
15         threadB.start();
16     }
复制代码

结果会显示:

 

posted @   kingsleylam  阅读(283)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示