`
bing1983333
  • 浏览: 5006 次
  • 性别: 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类加载初始化

    当一个类被加载、连接、初始化后,它的生命周期就开始了,当代表该类的Class对象不再被引用、即已经不可触及的时候,Class对象的生命周期结束。那么该类的方法区内的数据也会被卸载,从而结束该类的生命周期。一个类...

    项目中用到的jdbc连接 单例模式

    * 类载入到jvm的时候,会进行初始化,但是只会初始化static成员和static代码块, * 而不会初始化静态内部类的成员。因此,只有实际调用getInstance的时候, * 才会初始化静态内部类的静态成员。做到了延迟加载。 ...

    轻松搞定jvm类加载器

    类的加载、连接、初始化 1. 加载 通过类的包名和雷鸣查找到此类的字节码文件,将xx.class文件中的二进制数据读入到jvm内存,并存入其中的方法区内,然后利用字节码文件创建一个class对象存入到堆之中,用来封装类的...

    JVM—类加载过程学习

    其实,整个生命周期是7步,类从被加载到虚拟机内存中开始,到卸载出内存为止,分为:加载->验证->准备->解析->初始化->使用->卸载。 2 加载   加载分为三步: 1)通过全类名获取定义此类的二进制字节流; 2)将...

    关于JVM的总结

    加载->(验证->准备->解析)(连接)->初始化->使用->卸载 类被加载到虚拟机内存开始,到卸载出内存为止,生命周期包含: 加载,验证,准备,解析,初始化,使用,卸载 7个阶段,加载,验证,准备,初始化和卸载这5个...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    虚拟机类加载机制 / 171 7.1 概述 / 171 7.2 类加载的时机 / 172 7.3 类加载的过程 / 176 7.3.1 加载 / 176 7.3.2 验证 / 178 7.3.3 准备 / 181 7.3.4 解析 / 182 7.3.5 初始化 / 186 7.4 类加载器 ...

    深入理解JVM内存结构及运行原理全套视频加资料.txt

     第92讲 类加载的过程-初始化 00:19:41  第93讲 类加载器 00:22:41  第94讲 双亲委派模型 00:17:03  第95讲 运行时栈帧结构 00:08:46  第96讲 局部变量表 00:20:48  第97讲 操作数栈 00:08:36  第98讲...

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

    我觉得这里使用装载更好一点,第一,可以避免与类加载过程中的“加载”混淆,第二,装载体现的就是一个“装”字,仅仅是把货物从一个地方搬到另外一个地方而已,而这里的加载,却包含搬运货物、处理货物等一系列流程...

    深入理解Java虚拟机视频教程(jvm性能调优+内存模型+虚拟机原理)视频教程

    第92节类加载的过程-初始化00:19:41分钟 | 第93节类加载器00:22:41分钟 | 第94节双亲委派模型00:17:03分钟 | 第95节运行时栈帧结构00:08:46分钟 | 第96节局部变量表00:20:48分钟 | 第97节操作数栈00:08:36分钟 ...

    疯狂JAVA讲义

    5.3.2 成员变量的初始化和内存中的运行机制 128 5.3.3 局部变量的初始化和内存中的运行机制 130 5.3.4 变量的使用规则 130 5.4 隐藏和封装 132 5.4.1 理解封装 132 5.4.2 使用访问控制符 132 5.4.3 package和...

    resin-jvm 调优

    当jvm加载类时,永久域中的对象急剧增加,从而使jvm不断调整永久域大小。为了避免调整,可使用-XX:PerSize标志设置初始值。 下面把永久域初始值设置成32m,最大值设置成64m。 java -Xms512m -Xmx512m -Xmn128m -XX...

    玩转Java虚拟机(一)

    在Java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的 加载:查找并加载类的二进制数据,具体指将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一...

    学习JVM的第一天

    1.首先了解JVM(java虚拟机)、JRE和JDK之间的关系  JDK全称是Java SE Development Kit(Java开发工具... 在java代码中,类型的加载、连接与初始化过程都在程序运行期间完成的。  提供了更大的灵活性,增加了更多的可能

    Java反射在JVM的实现11

    3.1. Classloader加载过程ClassLoader用于加载、连接、缓存Class,可以通过纯Java或者native进行实现 3.2. 初始化过程当

    深入理解ClassLoader工作机制.docx

    JVM内存模型,类加载模式工作机制详细,内存屏障,类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三...

    涵盖了90%以上的面试题

    JVM加载class文件的原理 双亲委派模型 为什么要自定义类加载器 如何自定义类加载器 什么是GC 内存泄漏和内存溢出 Java的内存模型(JVM的内存划分) JVM内存模型1.7和1.8的区别 如何判断一个对象是否是垃圾对象 垃圾...

    Java虚拟机

    7.3.5 初始化 7.4 类加载器 7.4.1 类与类加载器 7.4.2 双亲委派模型 7.4.3 破坏双亲委派模型 7.5 本章小结 第8章 虚拟机字节码执行引擎 8.1 概述 8.2 运行时栈帧结构 8.2.1 局部变量表 8.2.2 操作数栈 ...

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    【JVM】类的初始化 58 类什么时候才被初始化: 58 类的初始化步骤: 59 【*JVM】什么是JVM线程死锁?JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息? 59 【*JVM】查看jvm...

    java 面试题 总结

    如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的...

    java面试宝典

    141、使用useBean标志初始化BEAN时如何接受初始化参数 36 142、使用JSP如何获得客户浏览器的信息? 36 143、能象调用子程序一样调用JSP吗? 36 144、当我重编译我的JSP使用的一个类后,为什么JVM继续使用我的老CLASS...

Global site tag (gtag.js) - Google Analytics