首页 / JAVA / Java 之 类的加载、连接和初始化
Java 之 类的加载、连接和初始化
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java 之 类的加载、连接和初始化,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含4533字,纯文字阅读大概需要7分钟。
内容图文
一、类的加载、连接和初始化
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM 将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载。
二、类的加载
系统可能在第一次使用某个类时加载该类,但也可能采用预先加载机制来预加载某个类,不管怎样,类的加载必须由类加载器完成,类加载器通常由 JVM 提供,由 JVM 提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承 ClassLoader 基类来创建自己的类加载器。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:
(1)从本地系统直接读取 .class 文件,这是绝大部分类的加载方法;
(2)从 zip,jar 等归档文件中加载 .class 文件,这种方式也很常见;
(3)通过网络下载 .class 文件或数据;
(4)从专有数据库中提取 .class 数据;
(5)将 Java 源文件数据上传到服务器中动态编译为 .class 数据,并执行加载。
但是,不管类的字节码内存从哪里加载,加载的结果都一样,这些字节码内容加载到内存后,都会将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口(即引用地址),所有需要访问和使用类数据只能通过这个 Class 对象。
小结:类的加载过程就是为了将字节码文件读取到内存中。
三、类的连接
当类被加载之后,系统为之生成一个对应的 Class 对象,接着将会进入连接阶段,连接阶段将会负责把类的二进制数据合并到 JVM 的运行状态之中。类的连接又可以分为如下三个阶段:
(1)验证:确保加载的类信息符合 JVM 规范,例如:以 cafe 开头,没有安全方法的问题;
(2)准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配,给非 final 的赋默认值,如果是 final的,直接赋常量值;
(3)解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
小结:加载和连接后,在方法区中已经有一个能够代表当前类的 Class 对象。
四、类的初始化
1、类的初始化主要就是对静态的类变量进行初始化
(1)执行 类构造器<clinit>() 方法的过程。类构造器<clinit>() 方法是由编译期自动收集类中所有类变量的显示赋值动作和静态代码块中的语句合并产生的。(①静态变量的显式赋值;②静态代码块的内容的组成)
(类构造器是构造类信息的,不是构造该类对象的构造器)
(2)当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发父类的初始化;
(3)虚拟机会保证一个类的<clinit>() 方法在多线程环境中被正确加锁和同步,即每一个类在内存中都只有唯一的一个 Class对象;
Demo:
1
class
Base{
2
private
static
int a = getNum();
3static{
4 ++a;
5 System.out.println("(2)a = " + a);
6 }
7static{
8 ++a;
9 System.out.println("(3)a = " + a);
10 }
11publicstaticint getNum(){
12 System.out.println("(1)a = " + a);
13return 1;
14 }
15}
1617class TestClinit extends Base{
18privatestaticint b = getNum();
19static{
20 ++b;
21 System.out.println("(5)b = " + b);
22 }
23static{
24 ++b;
25 System.out.println("(6)b = " + b);
26 }
27publicstaticint getNum(){
28 System.out.println("(4)b = " + b);
29return 1;
30 }
31publicstaticvoid main(String[] args) {
3233 }
34 }
运行结果:
(1)a = 0
(2)a = 2
(3)a = 3
(4)b = 0
(5)b = 2
(6)b = 3
注意:虽然类的加载大多数时候和类的初始化是一气呵成的,但其实类的加载不一定就会触发类的初始化。
2、类加载时会发生类的初始化情况
Java 程序首次通过下面5中方式来使用某个类时,系统就会初始化该类:
(1)当虚拟机启动,先初始化 main 方法所在的类
Demo:
1
//
当虚拟机启动,先初始化main方法所在的类
2
public
class
A {
3
static
{
4 System.out.println("init...A");
5 }
6publicstaticvoid main(String[] args) {
78 }
9 }
(2)new 一个类的对象
Demo:
1
//
new一个类的对象
2
class
B{
3
static
{
4 System.out.println("init...B");
5 }
6}
7publicclass TestB{
8publicstaticvoid main(String[] args) {
9new B();
10 }
11 }
(3)调用该类的静态变量(final的常量除外)和静态方法
Demo:
1
//
调用该类的静态变量(final的常量除外)和静态方法
2
class
C{
3
static
{
4 System.out.println("init...C");
5 }
6publicstaticvoid test(){
7 }
8}
9publicclass TestC {
10publicstaticvoid main(String[] args) {
11 C.test();
12 }
13}
1415//调用该类的静态变量(final的常量除外)和静态方法16class C{
17publicstaticint num = 10;
18static{
19 System.out.println("init...C");
20 }
21}
22publicclass TestC {
23publicstaticvoid main(String[] args) {
24 System.out.println(C.num);
25 }
26 }
(4)使用 java.lang.reflect 包的方法对类进行反射调用
Demo:
1
//
使用java.lang.reflect包的方法对类进行反射调用
2
class
D{
3
static
{
4 System.out.println("init...D");
5 }
6}
7publicclass TestD {
8publicstaticvoid main(String[] args) throws ClassNotFoundException {
9 ClassLoader cl = ClassLoader.getSystemClassLoader();
10 cl.loadClass("com.ks.loader.D");//该句不会造成类初始化,只是加载类11 System.out.println("类加载已完成...");
12 Class.forName("com.ks.loader.D");//会导致类初始化13 }
14 }
(5)当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
Demo:
1
//
当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
2
class
EBase{
3
static
{
4 System.out.println("父类初始化");
5 }
6}
7publicclass TestE extends EBase {
8static{
9 System.out.println("子类初始化");
10 }
11publicstaticvoid main(String[] args) {
12 }
13 }
3、类加载时不会发生类的初始化
(1)引用静态常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
Demo:
1
//
引用静态常量不会触发此类的初始化
2
class
NBase{
3
public
static
final
int MAX_VALUE = 100;
4static{
5 System.out.println("父类初始化");
6 }
7}
8class NSub extends NBase{
9static{
10 System.out.println("子类初始化");
11 }
12}
13publicclass TestNoInitialize {
1415publicstaticvoid main(String[] args) {
16 System.out.println(NSub.MAX_VALUE);
17 System.out.println(NBase.MAX_VALUE);
18 }
19 }
(2)当访问一个静态域时,只有真正声明这个域的类才会被初始化,当通过子类引用父类的静态变量,不会导致子类初始化;
Demo:
1
//
当访问一个静态域时,只有真正声明这个域的类才会被初始化
2
//
当通过子类引用父类的静态变量,不会导致子类初始化
3
class
NBase{
4
public
static
int num = 10;
5static{
6 System.out.println("父类初始化");
7 }
8}
9class NSub extends NBase{
10static{
11 System.out.println("子类初始化");
12 }
13}
14publicclass TestNoInitialize {
1516publicstaticvoid main(String[] args) {
17 System.out.println(NSub.num);
18 }
19 }
(3)通过数组定义类引用,不会触发此类的初始化。
Demo:
1
//
通过数组定义类引用,不会触发此类的初始化
2 NSub[] arr = new NSub[5];
原文:https://www.cnblogs.com/niujifei/p/12310744.html
内容总结
以上是互联网集市为您收集整理的Java 之 类的加载、连接和初始化全部内容,希望文章能够帮你解决Java 之 类的加载、连接和初始化所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。