JVM学习五:JVM之类加载器之编译常量和主动使用
在学习了前面几节的内容后,相信大家已经对JAVA 虚拟机 加载类的过程有了一个认识和了解,那么本节,我们就继续进一步巩固前面所学知识和特殊点。
一、类的初始化回顾
类在初始化的时候,静态变量的声明语句以及静态代码块都被看作类的初始化语句,Java虚拟机会按照初始化语句在类文件中的先后顺序来依次加载它们。
上图中a的初始化其实经过了四步
1、a被初始化为默认值0
2、a被赋予正确的初始值1
3、执行静态代码块,将a的值赋为2
4、执行静态代码块,将a的值赋为4
因此最终的执行结果是4,一个类只能被一个ClassLoader加载一次,只有没有被加载过的类或者已经被卸载的类才可能被再次加载。类的初始化步骤如下:
1、假如这个类还没有被加载和连接,那就先进行加载和连接
2、假如存在直接的父类,并且这个父类还没有被初始化则先初始化直接的父类
3、假如类中存在初始化语句,那就依次执行初始化语句。
注意:Java虚 拟机在初始化一个类的时候要求它的父类已经被初始化,但是这条规则并不适应于接口!在初始化一个类的时候并不会初始化他所实现的接口!在初始化一个接口的 时候也不会去初始化他的父接口!因此一个父接口并不会因为他的实现类或者子接口的初始化而初始化,只有当程序使用该接口特定的静态变量的时候才会去初始化 这个接口!
我们上面的例子印证了第三点,对于前两点我们知道我们构造一个类的时候假设它有父类,就会默认调用父类的无参构造方法,然后就初始化了父类,而初始化之前必须进行加载和连接!
我们来看一个具体的程序来验证上面的结论,代码如下:
class Parent{ static int a=1; static{ System.out.println("Parent's static block"); } } class Child extends Parent{ static int b=2; static{ System.out.println("Child's static block"); } } public class InitTestCase { static{ System.out.println("InitTestCase's static block"); } public static void main(String[] args) { System.out.println(Child.b); } }
这个程序很简单,我们一猜就能知道结果,因为是InitTestCase启动类,因此优先初始化!然后调用子类Child的b静态变量,Child继承自Parent,因此先初始化父类Parent,所以运行结果是:
修改代码如下:
class Parent{ static int a=1; static{ System.out.println("Parent's static block"); } } class Child extends Parent{ static int b=2; static{ System.out.println("Child's static block"); }
} public class InitTestCase { static{ System.out.println("InitTestCase's static block"); } public static void main(String[] args) { Parent parent; System.out.println("===== split line ====="); parent=new Child(); System.out.println(Parent.a); System.out.println(Child.b); } }
为了能够看清楚parent具体的初始化状态,我们加入split line来隔开程序段,这样又会返回怎样的结果呢?
刚开始的Parent是否会初始化?
我们之前已经说的很清楚了,只有主动使用的6种情况会导致类的初始化,因此刚开始parent不会初始化,因为InitTestCase是启动类,所以最先初始化,然后是分隔符,然后初始化子类Child,初始化Child的时候发现继承了Parent,所以先初始化Parent,然后初始化Child,然后再次调用parent的静态变量,因为Parent已经初始化了,一个类只能被一个ClassLoader初始化一次,所以不会初始化Parent,直接输出Parent.a的数据,Child同理,因此最终执行结果是:
这里也再次强调类初始化的实际为:
只有一下6种主动使用的情况会导致类的初始化,其他任何情况都被视为是被动使用,不会导致类的初始化!
1、创建类的实例
2、访问某个类或接口的静态变量,或者对该静态变量赋值
3、调用类的静态方法
4、反射(如Class.forName(“com.yhj.jvm.classloader.finalTest.TestCase”))
5、初始化一个类的子类
6、Java虚拟机启动时被标明为启动类的类(Java Test)
除了以上6种情况都属于被动使用,不会导致类的初始化。
再来看一段代码:
class StaticClassTest{ public static int staticValue = 2 / 1; static{ System.out.println("StaticClassTest's static block!"); } } public class TestCase { public static void main(String[] args) { System.out.println(StaticClassTest.staticValue); } }
很显然属于调用类的静态成员变量,类会被初始化,初始化就会执行执行静态初始化语句块,就会执行System.out.println("StaticClassTest's static block!");语句,因此运行结果如下:
但是如果这个类的静态成员是常量呢?
如下代码:
class StaticClassTest{ public static int staticValue = 2 / 1; static{ System.out.println("StaticClassTest's static block!"); } } //=================================================================== class StaticFinalTest1{ public final static int staticValue = 2 / 1; static{ System.out.println("StaticFinalTest1's static block!"); } } //=================================================================== class StaticFinalTest2{ public final static int staticValue = new Random().nextInt(10); static{ System.out.println("StaticFinalTest2's static block!"); } } //=================================================================== public class TestCase { public static void main(String[] args) { System.out.println(StaticClassTest.staticValue); System.out.println(StaticFinalTest1.staticValue); System.out.println(StaticFinalTest2.staticValue); } }
第一个我们已经知道了,会执行静态代码块,那第二个和第三个呢?他们的区别是第二个是在第一个的基础之上将staticValue变为了final,第三个对于第二个的区别是第三个static不是一个计算的具体值,而是0-10之间的一个随机数!又有什么样的区别呢?我们先来看一下运行结果!
二、编译时常量
看了上面的结果,应该很好奇,为什么没有输出:StaticFinalTest1's static block!
StaticFinalTest1的静态代码块没有执行,即StaticFinalTest1类并没有被初始化,而StaticFinalTest2被初始化了!
这是为什么呢?我们再来看一下他们的区别:StaticFinalTest1的静态常量值是2/1=2,对于这种值在编译的时候JVM就会把结果计算出来放进class文件中,相当于StaticFinalTest1=2,而StaticFinalTest2在编译的时候并不知道具体的数值是多少,需要运行的时候才会被赋值,所以我们可以看出,调用编译时的静态常量并不会初始化这个类(即不属于主动使用),而调用运行时的静态常量是会初始化该类的!
我们再来改动一下上面父子类的程序
class Parent{ static int a=1; static{ System.out.println("Parent's static block"); } } class Child extends Parent{ static int b=2; static{ System.out.println("Child's static block"); } } public class InitTestCase { public static void main(String[] args) { System.out.println(Child.a); } }
这样执行结果有什么呢?我们直接调用子类中父类定义的a会怎样呢?
按照我们之前的理论,执行结果应该是调用子类,先初始化父类,我们看下执行结果:
我们看到子类好像没有被初始化,不对,是确实没有被初始化!所以我们一定要注意:JVM规范规定只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用,所以我们直接调用Child中没有定义的a不属于对Child的主动使用,因此没有初始化Child!
三、ClassLoader
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
我们来看一下loadClass的API文档:
根据二进制名称来加载类,返回该类的Class对象的实例!那什么是二进制名称?
二进制名称我们可以理解为类的全称,如上图中的类、内部类、匿名内部类等!以前我们写的类都是通过启动类或者Web容器帮我们加载的,这里我们可以看到ClassLoader可以直接加载载这个类,但是我们看到这个类ClassLoader是抽象类。
也就是说我们无法通过new的方法创建实例,那我们该怎么做呢?
我们知道如果这个类不能实例化,我们可以通过他的静态方法访问这个类,我们来看一下ClassLoader的一个静态方法getSysytemClassLoader()这个方法
看一段程序:
class Test{ static{ System.out.println("Test's static block"); } } public class ClassLoaderTestCase { public static void main(String[] args) throws ClassNotFoundException { ClassLoader classLoader=ClassLoader.getSystemClassLoader(); System.out.println("ClassLoader:"+classLoader); Class<?> testClass=classLoader.loadClass("com.yhj.jvm.classloader.finalTest.Test"); System.out.println("using class.forName('com.yhj.jvm.classloader.finalTest.Test')"); testClass=Class.forName("com.yhj.jvm.classloader.finalTest.Test"); } }
这段程序执行下来,我们来看下运行结果
很显然,这段程序当调用loadClass的时候没有对类Test进行初始化,而当调用Class.forName的时候才被初始化,因此调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化!只有主动使用的6种情况才会初始化该类!
从加载到连接到初始化,我们看到loadClass只做加载和连接的操作,只有主动使用的6种才会对类进行初始化!
参考资料:
圣思园张龙老师深入Java虚拟机系列
JVM学习五:JVM之类加载器之编译常量和主动使用的更多相关文章
- JVM系列五:JVM监测&工具
JVM系列五:JVM监测&工具[整理中] http://www.cnblogs.com/redcreen/archive/2011/05/09/2040977.html 前几篇篇文章介绍了介 ...
- JVM学习笔记(四):类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 一.类加载的时机1. 类从被加载到虚拟机内存 ...
- JVM学习笔记-JVM模型
JVM学习笔记 == 标签(空格分隔): jvm 学习笔记全部来自于<深入理解java虚拟机>总结 jvm内存示意图 虚拟机栈(Java Virtual Machine Stacks): ...
- jvm学习006 jvm内存结构分配
主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念 一.JVM启动流程: JVM启动时,是由java命令/javaw命令来启动的. 二.JVM基本结构: JVM基本结构图: ...
- JVM体系结构之二:类加载器之2:JVM 自定义的类加载器的实现和使用
一.回顾一下jdk自带的类加载器: 1.java虚拟机自带的加载器 根类加载器(Bootstrap,c++实现) 扩展类加载器(Extension,java实现) 应用类加载器 ...
- 【JVM学习笔记】系统类加载器
可以通过“java.system.class.loader"属性指定系统类加载器 默认情况下,该属性值为空: public class Test { public static void m ...
- 【JVM学习笔记】扩展类加载器
扩展类加载器独有的特点,代码如下 public class Sample { } public class Test { static { System.out.println("Test ...
- JVM学习五:性能监控工具
一.系统性能监控 系统性能工具用于确定系统运行的整体状态,基本定位问题所在. Linux – uptime • 系统时间 • 运行时间 n 例子中为7分钟 • 连接数 n 每一个终端算一个连接 • 1 ...
- JVM学习--(五)垃圾回收器
上一篇我们介绍了常见的垃圾回收算法,不同的算法各有各的优缺点,在JVM中并不是单纯的使用某一种算法进行垃圾回收,而是将不同的垃圾回收算法包装在不同的垃圾回收器当中,用户可以根据自身的需求,使用不同的垃 ...
随机推荐
- OpenGL结合C#进行绘图
转自:http://www.cnblogs.com/wangshide/archive/2012/04/14/2447499.html 本人对OpenGL产生了浓厚的兴趣,又想学习一下C#这个语言,就 ...
- 【原】Spring源码浅析系列-导入源码到Eclipse
用了Spring几年,平时也断断续续在项目里看过一些源码,大多都是比较模糊的,因为一旦从一个地方进去就找不到方向了,只能知道它大概是做了什么事能达到这个功能或者效果,至于细节一般没有太深入去研究.后来 ...
- 从零一起学Spring Boot之LayIM项目长成记(四) Spring Boot JPA 深入了解
前言 本篇内容主要是一些关于JPA的常用的一些用法等.内容也是很多是看其他博客学来的,顺道在本系列博客里抽出一篇作为总结.下面让我们来看看吧. 不过我更推荐大家读本篇:https://lufficc. ...
- java SpringWeb 接收安卓android传来的图片集合及其他信息入库存储
公司是做APP的,进公司一年了还是第一次做安卓的接口 安卓是使用OkGo.post("").addFileParams("key",File); 通过这种方式传 ...
- springMVC web项目 对访问数据库的用户名密码进行加密解密
在使用springMVC开发web项目中,数据库的用户名,密码一般都是配置在.properties文件中 然后在通过.xml配置文件引入.properties的变量,例如 在config.proper ...
- C#图解教程 第八章 表达式和运算符
表达式和运算符 表达式字面量 整数字面量实数字面量字符字面量字符串字面量 求值顺序 优先级结合性 简单算术运算符求余运算符关系比较运算符和相等比较运算符递增运算符和递减运算符条件逻辑运算符逻辑运算符移 ...
- 【BZOJ2527】MET-Meteors(整体二分)
[BZOJ2527]MET-Meteors(整体二分) 题面 BZOJ权限题,良心洛谷链接 题解 其实我也不会做 看了zsy博客才会做... 这题如果直接爆算做显然行不通 如果只有单次询问,我们就可以 ...
- 【BZOJ4538】【HNOI2016】网络(树链剖分,线段树,堆)
题目链接,我是真的懒得调题目的格式... 题解 树链剖分搞一下LCA 把线段树弄出来 这只是形式上的线段树 本质上是维护一段区间的一个堆 每次把堆插入节点, 询问的时候查询线段树上的堆的最大值就行了 ...
- NOIWC2018滚粗记
Day0 \(THUWC\)刚刚结束..顺利的滚粗了 Day1 整理一下自己咸鱼的心 下午到学校坐车出发 这次队伍浩大THUWC只有4个 又到了雅礼洋湖这个地方 路上突然多了许多有关\(NOI\)的标 ...
- LightOJ1245 Harmonic Number (II)
题意 \(求\Sigma \lfloor \frac{n}{i} \rfloor\) Input starts with an integer T (≤ 1000), denoting the num ...