`
bing1983333
  • 浏览: 5117 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

【原】jvm中类的加载、连接与初始化

    博客分类:
  • jvm
阅读更多

直接看一个例程

/*【代码1】*/
public class JvmClassTest {
	
	public static JvmClassTest OBJ = new JvmClassTest();
	public static int A;
	public static int B = 0;
	//public static JvmClassTest OBJ = new JvmClassTest();
	
	static {
		System.out.println("A:" + A);
		System.out.println("B:" + B);
	}
	
	public JvmClassTest() {
		A++;
		B++;
	}
	
	public static void main(String[] args) throws Exception {
		JvmClassTest.B = -1;
	}

}

  看上去很绕,可以走读一遍自己给出一个打印结果。。。。

正确打印结果如上,这个看上去很诡异。A的值应该是int的默认值0,B显式赋值为0了,然后A++、B++,为何结果不同???稍微修改一下,将line4与line7互换一下

/*【代码2】*/
	//public static JvmClassTest OBJ = new JvmClassTest();
	public static int A;
	public static int B = 0;
	public static JvmClassTest OBJ = new JvmClassTest();

执行:

 如我所愿了。。。。只是调换了一下代码顺序而已,这是为什么呢?而且为什么只有B会受影响,而A不会受影响呢?解释这个问题只是需要去了解jvm在“真正执行一个java类的main()”之前,做了哪些事情?简单的说,有三件事情

  1. 加载
  2. 连接(其中又分解为验证、准备、解析三个步骤)
  3. 初始化

也许有些书上也有类似的图,而且每个词听上去都很不具体。“加载什么”、“准备什么”。。。。这是这篇blog想搞清楚的核心问题,下面就各个击破,分别对每一步讲一些之前了解到的理解

加载

它直接表现出来的代码应该是ClassLoader.getSystemClassLoader().loadClass("com.my.test.AbcClass")。所以具体完成类的加载工作的,是常被提到的类加载器ClassLoader,它就是专门干这件事的。“类的加载”具体而言就是指将类.class文件中的二进制数据读入到内存中将其放在方法区内,然后在堆区创建一个java.lang.Class对象,所以说“加载”的最终产出是堆中的一个Class对象,它一产生,加载这件事就干完了。这里又引出一个东东——方法区

上图截自毕玄大师的ppt,描绘了jvm内存的布局,书中都会说到方法区中存放的是类信息、类的field信息、方法信息都在其中;另外以前听到过一种说法:“堆(新生代+老生代)是留给java开发人员使用的,非堆(持久带即方法区)是留给jvm自己使用的”。再回过头看上面的描述——“类的加载”就是指将类的.class文件中的二进制数据读入到内存中将其放在方法区内,然后在堆区创建一个java.lang.Class对象,换句换说“类的加载”就是为了给程序员一个可以获得类相关定义信息的窗口,这个窗口就是Class对象,类加载的过程中将方法区的结构化类定义信息映射到堆里的一个实体Class对象中,进而程序员可以通过这道桥梁最终得到该类的一个实例,比如调用Class的newInstance()。

类的加载时机

目前我理解类的加载时机不受程序员控制,由jvm自己控制,或许它需要考虑一些优化策略,比如对于一些jvm认为未来很可能需要用到的类,jvm可以在空闲时提前加载,即提前准备好堆中的Class对象。类加载最迟的时机应该很明确,等同于类的初始化时机,下面说初始化时会说到。

连接

类的连接,就分开来讲它的每个子步骤吧

  1. 验证:顾名思义,这一步会做java基础语法检查、会做字节码验证、做二进制兼容的验证,这几个验证具体做的是什么,可能需要专门深挖了。总之,验证就是做各种验证涉及类文件、字节码、语法语义等等各方面
  2. 准备:这一步理解起来很具体,就是为类的static变量开辟堆内存,并赋上默认值(java各类型的默认值不同)
  3. 解析:在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用。比如在A类中实例化了B类的一个对象并调了一个方法,b.test(),在解析之前,这个b.test()只是指向一个描述符(该描述符存在Class对象专属的独立常量池中),解析之后,它就被替换成一个真实的方法指针,指向方法区中B类test()方法的那一段(这一段内存相当于描述了test方法应该如何执行),这样未来才能完成方法的执行动作

类的连接时机

查了很久,没有查到,目前暂时yy认为他是跟在类加载后面的吧。。。

初始化

千辛万苦走到最后一步初始化。类的初始化可以用一句明确的话来描述——“就是按顺序把类中的static代码执行一遍”,若是对static变量赋值,则进行赋值操作(连接时只是开辟内存并给默认值,此时才给static变量赋上我们代码里写的初始值);若是static代码块,则将static块执行一遍。另外值得重点说明的是两点,初始化的时机与初始化的步骤

类的初始化时机

在以下6种场景下,才会触发“类的初始化”这件事

  1. 创建类的实例
  2. 访问类或接口的statis变量,或者对statis变量赋值
  3. 调用类的statis方法
  4. 调用反射(如Class.forName(“com.my.test.Test”))
  5. 初始化一个类的子类
  6. Jvm启动时被标明为启动类的类(就是命令行执行java时指定的那个带有main方法的类,就是启动类)

类的初始化步骤

下图可以看出,若发生上面的6个情况中的任何1个,会触发了类的初始化,若此时该类还没有做加载&连接,会连带触发做加载&连接,但是另一种情况是在此之前jvm已经未雨绸缪地提前完成了加载&连接,这个自然是jvm设计者期望看到的情况。下图可以看出,父类有优先的初始化权利。

 
-------------------------------------------分割线-------------------------------------------
 
说完以上这些,开篇的疑惑就可以解除了。。。
【代码1】的执行顺序如下:
 A)  加载&连接类JvmClassTest,“连接”中的“准备”过程会为分别为三个static变量开辟对内存,并给默认值
  1. int A 开辟4字节,给默认值0
  2. int B 开辟4字节,给默认值0
  3. JvmClassTest OBJ 给默认值null
 B) 下面进入类的初始化阶段,按顺序做static动作
  1. static JvmClassTest OBJ = new JvmClassTest()调用了构造方法,故A++B++,此时A由0->1,B由0->1
  2. static int A这句无赋值动作,什么也不做
  3. static int B = 0,这句B由1->0
  4. 最终,static代码块打印出了刚才看到的 A:1,B:0
 C) 初始化完成之后,开始执行main方法,B由0->-1。完毕,jvm进程终止。
 
而【代码2】中只是相当于将1.放到了3.之后,所以得到了不同的结果。
 
最后还有一个问题可以问一下自己,上面这个代码对应到“触发类初始化的6个场景”中,应该对上哪一条呢?“2.访问类或接口的statis变量,或者对statis变量赋值”,看上去是这个,是吗?通过代码还是可以验证的,只要在main()方法第一句加一个打印
/*【代码3】*/
	public static void main(String[] args) throws Exception {
		System.out.println("test");
		JvmClassTest.B = -1;
	}
执行结果如下:
发现static块先执行了,而后才执行的System.out.println("test"),当时我看到这个结果之后,才恍然大悟,“6. Jvm启动时被标明为启动类的类”,Eclipse执行时将该类作为启动类拉,java命令一执行,当场就触发了JvmClassTest类的初始化,进而连带触发了加载&连接动作,而后执行JvmClassTest类的初始化,初始化完毕之后才开始步入main()方法体(此时此刻,static代码块已经在控制台打出了A、B的值了。。。),可能例程代码可以更纯粹一点。。。。。
/*【代码4】*/
public class JvmClassTest {
	
	//public static JvmClassTest OBJ = new JvmClassTest();
	public static int A;
	public static int B = 0;
	public static JvmClassTest OBJ = new JvmClassTest();
	
	static {
		System.out.println("A:" + A);
		System.out.println("B:" + B);
	}
	
	public JvmClassTest() {
		A++;
		B++;
	}
	
	public static void main(String[] args) throws Exception {
		//无
	}

}
 
终。
  • 大小: 20.6 KB
  • 大小: 9.4 KB
  • 大小: 25.1 KB
  • 大小: 9.1 KB
  • 大小: 35.7 KB
  • 大小: 41.3 KB
  • 大小: 9.8 KB
7
4
分享到:
评论
7 楼 iamzhongyong 2012-02-26  
超哥的博客好给力啊呵呵,第一篇文章就有这么多人看呵呵。佩服
6 楼 ls8023 2012-02-21  
受教了,不错
5 楼 zzz065 2012-02-20  
很不错~
4 楼 thebye85 2012-02-20  
写的不错,学习了
3 楼 snowolf 2012-02-20  
值得学习!
2 楼 ffychina 2012-02-20  
补充一下,类的释放时机。尽管你把所有的实例都释放了,也GC了,但是,类并不会立即释放,除非内存不足时才有可能会被释放。可以通过jconsole.exe查看类加载信息。
所以java在首次加载类并实例化时会比较慢,第二次就快很多了。我以前写的JAVA2JS编译器,第一次编译花了8秒,第二次才0.1秒,性能相差非常大。也幸好有赖于java的类缓存机制解决了编译时间过长的问题,否则优化代码都不知道要花多少精力和时间才搞得定,因为编译处理太复杂了。
1 楼 liwx2000 2012-02-19  
博主写的太好了,学习了,膜拜~

相关推荐

    Java虚拟机JVM类加载初始化

    Java虚拟机JVM类加载初始化是Java程序运行过程中的关键环节,它负责将类的字节码文件加载到内存中并进行相应的处理,以便程序能够正确执行。在Java中,类加载器(Classloader)扮演着核心角色。下面将详细讨论类加载...

    JVM实战-JVM类加载机制案例分析

    通过实践操作,掌握类的加载、连接与初始化过程,了解不同类型的类加载器及其工作原理,学会创建自定义的类加载器,并对类的卸载有所认识。实验将结合具体的Java程序实例,运用单例模式对静态变量和对象进行初始化,...

    JVM类加载分析

    这篇博客将探讨JVM如何加载、连接和初始化类,以及这一过程中的关键概念和技术。 一、类加载器 类加载器是JVM实现动态加载的核心组件,它的主要任务是根据类名找到对应的.class文件,并将其转换为内存中的Class...

    Java类加载连接和初始化原理解析

    Java类加载连接和初始化是Java虚拟机(JVM)中一个非常重要的机制,它们共同完成了类从加载到初始化的整个过程。下面我们将详细介绍Java类加载连接和初始化原理分析。 一、类加载 类加载是Java虚拟机将类从.class...

    Java虚拟机JVM类加载学习笔记

    本文主要探讨JVM的类加载机制,包括类加载、连接、初始化等关键过程,以及类的主动使用和被动使用的情况。 首先,我们要理解**类加载**的作用。JVM的类加载器(ClassLoader)负责将编译后的`.class`文件加载到内存...

    【JVM】类的奇幻漂流——类加载机制探秘

    最后是初始化阶段,这是类加载的最后一步。在这个阶段,执行类初始化块(方法),初始化静态变量为程序员指定的值,而非默认值。同时,如果类中有静态块,也会在此时执行。 通过以上三个阶段,一个类被完全加载到...

    Java类的加载连接和初始化实例分析

    Java类的加载、连接和初始化是Java虚拟机(JVM)中的一些重要步骤,它们对Java类的生命周期产生了深远的影响。在本文中,我们将详细介绍Java类的加载、连接和初始化的原理和实现技巧,并通过实例来分析Java类的加载...

    详解Java 类的加载、连接和初始化

    Java 类的加载、连接和初始化是 Java 编程语言中最基本的概念,理解这三个过程对于深入学习 Java 是非常重要的。本文将详细介绍 Java 类的加载、连接和初始化的相关资料,帮助大家更好地理解和学习。 Java 类的加载...

    深入java虚拟机加载初始化

    Java程序在运行过程中涉及几个关键的概念,包括类加载、连接以及初始化等阶段。 1. **类加载**(Load):这是类生命周期的第一个阶段,主要任务是将编译好的字节码文件(`.class`文件)加载到内存中。这个过程具体...

    深入JVM概要 JVM详解

    类加载器是JVM中负责加载类的组件。类加载器可以是启动类加载器,也可以是用户自定义的类加载器。类加载器的作用是将类加载到Java虚拟机中。 垃圾收集器 垃圾收集器是JVM中的垃圾回收机制。垃圾收集器的作用是回收...

    深入java虚拟机(三)——类的生命周期(下)类的初始化1

    在Java虚拟机(JVM)中,类的初始化会在类加载和连接完成后发生。 初始化阶段的目标是确保类的静态成员被正确地初始化。有两种主要的方式进行类的初始化: 1. **静态变量的声明处赋值**:这是最直接的方式,如`...

    类加载机制与JDK调优命令.pdf

    这一过程涉及五个主要步骤:加载、连接、初始化、使用和卸载。首先,类加载器将.class文件加载到内存中;接着,进行连接操作,包括验证字节码文件的正确性、为类的静态变量分配内存并赋予默认值;然后,加载类所引用...

    18.类加载器.doc

    类加载器在Java虚拟机(JVM)中扮演着至关重要的角色,它是程序运行的基础,负责将类的二进制数据加载到内存中并进行初始化。类加载的过程分为三个主要阶段:加载、连接和初始化。 1. **加载**:在这个阶段,类加载...

    深入java虚拟机(二)——类的生命周期(上)类的加载和连接1

    本文将重点探讨类加载器的角色,以及类的加载、连接和初始化这三个阶段。 1. **类加载**: 类加载是JVM获取Java类字节码文件并将其转换为内存中`java.lang.Class`对象的过程。这个过程通常由类加载器完成。Java源...

    java类加载器

    3. **初始化**:执行类构造器`()`方法,为类变量赋初始值。 #### 三、类与类加载器的关系 类加载器对于确定类在JVM中的唯一性至关重要。两个类只有当它们来自相同的类加载器时才被认为是相同类的不同实例。这意味...

    Java类加载内幕详细讲解

    类加载的过程主要分为三个阶段:加载(Loading)、连接(Linking)和初始化(Initialization)。 1. **加载**(Loading): - 类加载器查找并加载类的二进制流。 - 创建`java.lang.Class`对象,代表这个类。 2. ...

    Java起航 ---- 类的初始化历程

    在Java编程语言中,类的初始化是一个至关重要的过程,它涉及到类加载、连接以及初始化等多个阶段。本篇文章将深入探讨“Java起航——类的初始化历程”,并结合JVM(Java虚拟机)的工作原理,帮助你更好地理解这个...

    jvm工作机制

    ### Java运行过程:类的加载、连接与初始化 Java程序的执行过程可以分为三个主要阶段:类的加载、连接以及初始化。这些阶段共同构成了JVM对类的准备过程,确保了类的正确性和可用性。 #### 加载 加载阶段的主要...

    java初始化和清理

    Java的初始化包括类的加载、连接和初始化三个过程,而清理则主要指垃圾回收机制。 首先,Java初始化的主要特性表现在类的加载过程上。与其他一些传统编程语言(如C++)不同,Java的类并不是在程序启动时立即加载的...

    深入理解JVM之类加载机制详解

    类的加载和连接是通过类加载器来实现的,包括加载、验证、准备、解析和初始化五个阶段。类的初始化和使用是通过类的实例化和方法调用来实现的。 6. 类加载机制的优点和缺点 类加载机制的优点是提供了高度的灵活性...

Global site tag (gtag.js) - Google Analytics