深入java (类)初探类的加载和初始化顺序

来源:互联网 发布:一路发微博营销软件 编辑:程序博客网 时间:2024/06/04 19:58

上一篇博客深入理解了java中类的继承的作用和意义:复用和规范,我们也对重写父类的方法规范做出了解释:1.返回值和参数类型、参数个数和参数顺序必须一样2.而且方法访问的权限一定要越来越大或者相等,因为如果你的父类方法是public,而子类的方法是private,程序在编译器可以通过,在执行期间突然变得不可读写和存取(多态机制),这显然是不合理的,同时编译器也不会允许你这样做。这是java的多态机制导致的,这也是为了实现多态机制必须做出的牺牲。这一篇文章继续,初探类的加载和初始化顺序。待我撸完深入java虚拟机再深撸一把。原创不易,转载请注明出处:http://blog.csdn.net/yabay2208

一:初探类的加载

Java语言的哲学:一切都是对象。对于Java虚拟机而言,一个普通的Java类同样是一个对象,那如果是对象,必然有它的初始化过程。一个类在JVM中被实例化成一个对象,需要经历三个过程:加载、链接和初始化。

JAVA类的加载:从字节码二进制文件.class文件将类加载到内存,从而达到类的从硬盘上到内存上的一个迁移,所有的程序必须加载到内存才能工作。一个Java类在被加载到内存后会在Java堆中创建一个类(java.lang.Class)对象,同时JVM为每个类对象都维护一个常量池,常量池用于保存静态的数据(类似于符号表)。
在Class文件结构中,最头的4个字节用于存储魔数Magic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个U2类型的数据(constant_pool_count)存储常量池容量计数值。
常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

  • 类和接口的全限定名
  • 字段名称和描述符
  • 方法名称和描述符

关于常量池:方法区中的运行时常量池,运行时常量池是方法区的一部分。
CLass文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。
常量池的好处:
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

JVM对常量的优化可以看一下我的这一篇文章的第二部分:
http://blog.csdn.net/yabay2208/article/details/71248462
对static的优化是这一篇:
http://blog.csdn.net/yabay2208/article/details/71172896

二:类的初始化顺序

1.通过子类来调用父类的静态字段,只会触发父类的初始化,但是这是要看不同的虚拟机的不同实现。例子如下(撸完深入java虚拟机再深究。):
这里写图片描述
运行结果:
这里写图片描述
因为当我们调用Son.age的时候,static age在内存中只有一份,所以jvm会认为当我们执行Son.age的时候,必须要初始化Father类,这样才可以访问到age变量,这个时候Father类完成初始化Static代码块执行完毕。但是因为Son和Father的Static int age在内存中只有一份,既然只有一份那就不初始化Son了,但是当我们调用Son.high的时候,jvm这个时候才会初始化Son类,输出“Son’s static CodeBlock”;然后对age变量赋值,因为Father和Son的Static int age在内存中只有一份,所以当我们输出Father.age的时候,一样是55。

2.当子类主动访问的时候,会引起其父类的初始化例子如下:
这里写图片描述
运行结果:
这里写图片描述

从这里我们就会发现,类加载完毕之后,是不一定会进行初始化的。

三:我们可以调用java.lang.Class.forName(…)加载类

动态加载类有两种方法:Class.forName()与ClassLoader.loadClass()
今天先分析Class.forName()

public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException 

这里的initialize参数很重要。表示在加载同时是否完成初始化的工作(说明:单参数版本的forName方法默认是完成初始化的)。有些场景下需要将initialize设置为true来强制加载同时完成初始化。例如典型的就是利用DriverManager进行JDBC驱动程序类注册的问题。因为每一个JDBC驱动程序类的静态初始化方法都用DriverManager注册驱动程序,这样才能被应用程序使用。这就要求驱动程序类必须被初始化,而不单单被加载。Class.forName的一个很常见的用法就是在加载数据库驱动的时候。如 Class.forName(“org.apache.derby.jdbc.EmbeddedDriver”).newInstance()用来加载 Apache Derby 数据库的驱动。

下一篇文章咱们说一说对象初始化时,类构造函数的调用顺序以及多态。

0 0