jvm - 类的初始化过程
我们知道,我们写的java代码称为源码,想要能够被jvm执行首先需要编译成.class文件,那么编译完到使用又都经理的哪些阶段呢?主要分为以下三个阶段:
- 加载:查找并加载类的二进制数据(.class文件硬盘到内存的一个过程)。
- 连接
- 验证:确保被加载的类的正确性。
- 准备:为类的 静态变量分配内存,并将其初始化为默认值。
- 解析:把类中的符号引用转换为直接引用。 - 初始化:为类的静态变量赋予正确的初始值。
实例(一)
class Singleton1 {
private static Singleton1 singleton = new Singleton1();
public static int counter1;
public static int counter2 = 0;
public Singleton1() {
counter1++;
counter2++;
}
public static Singleton1 getInstance() {
return singleton;
}
}
class Singleton2 {
public static int counter1;
public static int counter2 = 0;
private static Singleton2 singleton = new Singleton2();
public Singleton2() {
counter1++;
counter2++;
}
public static Singleton2 getInstance() {
return singleton;
}
}
public class Test0010 {
public static void main(String[] args) {
Singleton1 singleton1 = Singleton1.getInstance();
System.out.println(singleton1.counter1);
System.out.println(singleton1.counter2);
Singleton2 singleton2 = Singleton2.getInstance();
System.out.println(singleton2.counter1);
System.out.println(singleton2.counter2);
}
}
结果:

如果和你想的不一样的话,不要急,继续往下看:
Java程序对类的使用方式可分为两种
- 主动使用
- 被动使用
主动使用
- 创建类的实例
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(如Class.forName(“com.xxx.Test”))
- 初始化一个类的子类
- Java虚拟机启动时被标明为启动类的类(Java ClassA)
除了主动使用外的其他方式都是被动使用。所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们。会过头来解析上面的例子:
我们在使用一个类之前首先要经历:加载->链接->初始化的过程,而在链接阶段又有验证、准备、解析几个过程,重点是准备阶段:为类的 静态变量分配内存,并将其初始化为默认值。这里很重要,也就是我们在首次主动使用类之前,类中的静态变量已经被初始化为了默认值,而在真正的主动使用时才会将静态变量初始化为应有的初始值。来看类Singleton1,在链接阶段过后,类中静态成员的情况是这样子的:
class Singleton1 {
private static Singleton1 singleton = null;
public static int counter1 = 0;
public static int counter2 = 0;
}
在Test0010的main方法中,我们调用了Singleton1的如下方法和成员,这里是对Singleton1类的首次主动使用,会引发类的初始化。
Singleton1 singleton1 = Singleton1.getInstance();
System.out.println(singleton1.counter1);
System.out.println(singleton1.counter2);
初始化执行过程中主要执行类的构造方法,并且按照顺序执行里面的静态语句和静态代码块。
执行private static Singleton1 singleton = new Singleton1(); 时,counter1和counter2均自增,此时counter1和counter2的值均为1,继续往下执行时counter2 又被赋值为0,所以最终
counter1和counter2的值分别为1和0。同样的道理不难看出Singleton2中初始化操作顺序执行静态语句之后,counter1和counter2的值分别为1和1。
实例(二)
class FinalTest1 {
/**
* 编译时常量,访问它不会初始化这个类
*/
public static final int x = 100 / 2;
static {
System.out.println("final block ~~~");
}
}
class FinalTest2 {
/**
* 运行时常量,访问它会初始化这个类
*/
public static final int x = new Random().nextInt(100);
static {
System.out.println("final block ~~~");
}
}
public class Test0020 {
public static void main(String[] args) {
System.out.println(FinalTest1.x);
System.out.println(FinalTest2.x);
}
}

我们看到,FinalTest1 中的静态代码块没有执行,而FinalTest2中的静态代码块执行了。
结论:首次主动访问编译时常量,不会初始化这个类;而首次主动访问运行时常量,会初始化这个类。
同时要注意:静态代码块在类初始化时候才会执行。
实例(三)
public class Test0030 {
static {
System.out.println("main static block");
}
public static void main(String[] args) {
System.out.println(Child.b);
}
}
class Parent {
static {
System.out.println("parent static block");
}
}
class Child extends Parent {
static int b = 4;
static {
System.out.println("child static block");
}
}

结论: 当初始化一个类时,会先初始化这个类的父类。
实例(四)
public class Test0050 {
public static void main(String[] args) {
System.out.println(Child.a);
}
}
class Parent {
static int a = 3;
static {
System.out.println("parent static block");
}
}
class Child extends Parent {
static {
System.out.println("child static block");
}
}

结论:有当程序访问的静态变量或静态方法确实在当前类或接口中定义时,才可以认为是对类的主动使用。
实例(五)
public class Test0060 {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class clazz = classLoader.loadClass("com.chengli.jvm.classloader.v0060.CL");
System.out.println("------------------------------");
clazz = Class.forName("com.chengli.jvm.classloader.v0060.CL");
}
}
class CL {
static {
System.out.println("class CL static block");
}
}

结论:调用ClassLoader的loadClass方法加载一个类,并不是对类的主动使用
jvm - 类的初始化过程的更多相关文章
- 【深入理解Java虚拟机】类的初始化过程
类的初始化过程 类的加载过程.png 加载 将 Class 文件以二进制的形式加载到内存中 验证 校验 Class 文件是否安全,是否被正确的修改等 准备 为类变量申请内存,设置默认值,(初始化变量的 ...
- Red5源代码分析 - 关键类及其初始化过程
原文地址:http://semi-sleep.javaeye.com/blog/348768 Red5如何响应rmpt的请求,中间涉及哪些关键类? 响应请求的流程如下: 1.Red5在启动时会调用RT ...
- JVM -- 类的初始化
<深入理解Java虚拟机> 第二版中介绍到了类的加载过程. 一个类从加载入内存到卸载出内存为止,整个生命周期包括: Loading(加载)-----Verification(验证)---- ...
- Java类的初始化过程及清理
一.类的数据成员初始化 Java中类的数据成员初试化可能有两种形式. 在定义类成员变量的地方直接提供初始化值(这是C++中不允许的) 在构造器中初试化.(Java中不存在类似C++中的初始化列表) 两 ...
- java类的初始化过程
1 先初始化父类的静态成员和静态块,然后初始化子类的静态成员和静态块,然后再初始化父类,然后再初始化子类. 2 先初始化父类 3 单个类初始化的顺序 先初始化成员变量和代码块,后调用构造函数 4 如果 ...
- 类的初始化过程(难点)--------java基础总结
前言:看到这么好的东西,忍不住又写到了博客上面 Student s = new Student();在内存中究竟做了哪些事情呢? ①加载student.class文件进内存. ②为栈内存s开辟空间. ...
- 深入理解Java对象的创建过程:类的初始化与实例化
摘要: 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类 ...
- JVM类生命周期概述:加载时机与加载过程
一个.java文件在编译后会形成相应的一个或多个Class文件,这些Class文件中描述了类的各种信息,并且它们最终都需要被加载到虚拟机中才能被运行和使用.事实上,虚拟机把描述类的数据从Class文件 ...
- 深入学习Java对象创建的过程:类的初始化与实例化
在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完 ...
随机推荐
- php MP3文件下载功能的实现
方式一:生成文件,返回一个链接,window.href = 链接: 方式二:hearder输出文件流. 先设置流的Content-Type和web服务器的mime类型. mime类型参考 一个head ...
- 终于等到你,最强 IDE Visual Studio 2017 正式版发布
Visual Studio 2017 正式版发布,该版本不仅添加了实时单元测试.实时架构依赖关系验证等新特性,还对许多实用功能进行了改进,如代码导航.IntelliSense.重构.代码修复和调试等等 ...
- TIScript 代码Demo
var filelist = null; function alert(msg) { view.msgbox(null,msg); } self.on("click", " ...
- spring框架学习感悟
学习了一段时间的spring,但是在练习时老是出现bug,一方面,框架封装了很多东西,简化了开发,但是万一出现问题,就很难排查.这说明应该找个慢慢的熟悉它,并且掌握它. 在这个过程中,可能要不断地试错 ...
- LeetCode 11 - 盛最多水的容器 - [双指针暴力]
题目链接:https://leetcode-cn.com/problems/container-with-most-water/description/ 给定 n 个非负整数 $a_1,a_2,\cd ...
- [No0000136]6个重要的.NET概念:栈,堆,值类型,引用类型,装箱,拆箱
引言 本篇文章主要介绍.NET中6个重要的概念:栈,堆,值类型,引用类型,装箱,拆箱.文章开始介绍当你声明一个变量时,编译器内部发生了什么,然后介绍两个重要的概念:栈和堆:最后介绍值类型和引用类型,并 ...
- [No0000D2]ClearCSharp编程清理脚本批处理bat
for /f "tokens=*" %%a in ('dir obj /b /ad /s ^|sort') do rd "%%a" /s/q for /f &q ...
- 用CSS画基本图形
用CSS画基本图形 1.正方形 代码如下: #square { width: 100px; height: 100px; background: red; } 2.长方形 代码如下: #recta ...
- 创建本地SVN版本库以及将SVN导入GIT
创建本地SVN 通常SVN作为一种服务,是在服务器上架设,供用户通过网络访问使用.但如果只是自己日常使用,完全可以架设在本机上,不需要启动后台程序,通过文件的方式访问即可. 建立本地SVN非常简单,一 ...
- 原码,补码,反码的概念及Java中使用那种存储方式
原码,补码,反码的概念及Java中使用那种存储方式: 原码:原码表示法是机器数的一种简单的表示法.其符号位用0表示正号,用:表示负号,数值一般用二进制形式表示 补码:机器数的补码可由原码得到.如果机器 ...