文档章节

看懂Class文件的装载流程

就为这题
 就为这题
发布于 2017/02/28 10:16
字数 1882
阅读 9
收藏 0
点赞 0
评论 0

1 Class文件的装载流程

只有被java虚拟机装载的Class类型才能在程序中使用(注意装载和加载的区别

1.1 类装载的条件

 Class只有在必须要使用的时候才会被装载,Java虚拟机不会无条件的装载Class类型。Java虚拟机规定:一个类或者接口在初次使用时,必须进行初始化。这里的使用指的是主动使用,主动使用有以下几种情况:

  • 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
  • 当调用类的静态方法时,即当使用了字节码invokestatic指令
  • 当使用类或者接口的静态字段时(final常量除外),即使用getstatic或者putstatic指令
  • 当使用java.lang.reflect包中的方法反射类的方法时
  • 当初始化子类时,必须先初始化父类
  • 作为启动虚拟机、含有main方法的那个类

除了以上情况属于主动使用外,其他情况均属于被动使用,被动使用不会引起类的初始化

 

例1:主动使用

public class Parent{

  static{

    System.out.println("Parent init");

  }

}

public class Child{

  static{

    System.out.println("Child init");

  }

}

public class InitMain{

  public static void main(String[] args){

    Child c = new Child();

  }

}

以上声明了3个类:Parent Child InitMain,Child类为Parent类的子类。若Parent被初始化,根据代码中的static块可知,将会打印"Parent init",若Child被初始化,则会打印"Child init"。执行InitMain,结果为:

Parent init 

Child init

由此可知,系统首先装载Parent类,接着装载Child类。符合主动装载中的两个条件,使用new关键字创建类的实例会装载相关的类,以及在初始化子类时,必须先初始化父类。

例2 :被动装载

public class Parent{

  static{

    System.out.println("Parent init ");

  }

  public static int v = 100; //静态字段

}

public class Child extends Parent{

  static{

    System.out.println("Child init");

  }

}

public class UserParent{

  public static void main(String[] args){

    System.out.println(Child.v);

  }

}

Parent中有静态变量v,并且在UserParent中,使用其子类Child去调用父类中的变量。

运行代码:

Parent init

100

虽然在UserParent中,直接访问了子类对象,但是Child子类并未初始化,只有Parent父类进行初始化。所以,在引用一个字段时,只有直接定义该字段的类,才会被初始化。

注意:虽然Child类没有被初始化,但是,此时Child类已经被系统加载,只是没有进入初始化阶段。

可以使用-XX:+ThraceClassLoading 参数运行这段代码,查看日志,便可以看到Child类确实被加载了,只是初始化没有进行

 

例3 :引用final常量

public class FinalFieldClass{

  public static final String constString = "CONST";

  static{

    System.out.println("FinalFieldClass init");

  }

}

public class UseFinalField{

  public static void main(String[] args){

    System.out.println(FinalFieldClass.constString);

  }

}

运行代码:CONST

FinalFieldClass类没有因为其常量字段constString被引用而初始化,这是因为在Class文件生成时,final常量由于其不变性,做了适当的优化。

分析UseFinalField类生成的Class文件,可以看到main函数的字节码为:

在字节码偏移3的位置,通过Idc将常量池第22项入栈,在此Class文件中常量池第22项为:

#22 = String        #23     //CONST

#23 = UTF8         CONST

由此可以看出,编译后的UseFinalField.class中,并没有引用FinalFieldClass类,而是将其final常量直接存放在常量池中,因此,FinalFiledClass类自然不会被加载。(javac在编译时,将常量直接植入目标类,不再使用被引用类)通过捕获类加载日志(部分日志)可以看出:

注意:并不是在代码中出现的类,就一定会被加载或者初始化,如果不符合主动使用的条件,类就不会被初始化。

 

1.2 类装载的整个过程

1)加载类

加载类处于类装载的第一个阶段。

加载类时,JVM必须完成:

  • 通过类的全名,获取类的二进制数据流
  • 解析类的二进制数据流为方法区内的数据结构
  • 创建java.lang.Class类的实例,表示该类型

2)连接

 1 验证类:

当类被加载到系统后,就开始连接操作,验证是连接的第一步。

主要目的是保证加载的字节码是符合规范的。验证的步骤如图:

2 准备

当一个类验证通过后,虚拟机就会进入准备阶段,在这个阶段,虚拟机会为这个类分配相应的内存空间,并设置初始值。

java虚拟机为各种类型变量默认的初始值如表:

类型 默认初始值
int 0
long 0L
short (short)0
char \u0000
boolean false
reference null
float 0f
double 0f

 

 

 

 

 

 

 

 

 注意:java并不支持boolean类型,对于boolean类型,内部实现是Int,由于int的默认值是0,故对应的,boolean的默认值是false

如果类属于常量字段,那么常量字段也会在准备阶段被附上正确的值,这个赋值属于java虚拟机的行为,属于变量的初始化。事实上,在准备阶段,不会有任何java代码被执行。

3 解析类

在准备阶段完成后,就进入了解析阶段。

解析阶段的任务就是将类、接口、字段和方法的符号引用转为直接引用。

符号引用就是一些字面量的引用,和虚拟机的内部数据结构和内存布局无关。比较容易理解的就是在Class类文件中,通过常量池进行大量的符号引用。

具体可以使用JclassLib软件查看Class文件的结构:::

 

3)初始化

初始化时类装载的最后一个阶段。如果前面的步骤没有出现问题,那么表示类可以顺利装载到系统中。此时,类才会开始执行java字节码。

初始化阶段的重要工作是执行类的初始化方法<clinit>。方法<clinit>是由编译器自动生成的,它是由类静态成员的赋值语句以及static语句块合并产生的。

 例如:

public class SimpleStatic{

  public static int id = 1;

  public static int number;

  static{

    number = 4;

  }

}

java编译器为这段代码生成如下的<clinit>:

0 iconst_1
1 putstatic #2 <Demo.id>
4 iconst_4
5 putstatic #3 <Demo.number>
8 return

可以看出,生成的<clinit>函数中,整合了SimpleStatic类中的static赋值语句以及static语句块,先后对id和number两个成员变量进行赋值

由于在加载一个类之前,虚拟机总是会试图加载该类的父类,因此父类的<clinit>总是在子类<clinit>之前被调用。也就是说,子类的static块优先级高于父类。

public class ChildStatic extends Demo{
  static{
    number = 2;
  }
  public static void main(String[] args){
    System.out.println(number);
  }
}

运行可知:

2

说明父类的<clinit>总是在子类<clinit>之前被调用。

注意:java编译器并不是为所有的类都产生<clinit>初始化函数,如果一个类既没有赋值语句,也没有static语句块,那么生成的<clinit>函数就应该为空,因此,编译器就不会为该类插入<clinit>函数

例如:

public class StaticFinalClass{

  public static final int i=1;

  public static final int j=2;

}

由于StaticFinalClass只有final常量,而final常量在准备阶段初始化,而不在初始化阶段处理,因此对于StaticFinalClass类来说,<clinit>就无事可做,因此,在产生的class文件中没有该函数存在。

 

© 著作权归作者所有

共有 人打赏支持
就为这题
粉丝 9
博文 33
码字总数 37965
作品 0
海淀
程序员
jBPM流程定义语言(JPDL)

JPDL指定了xml模式和打包所有流程定义相关文件到一个流程档案的机制。 16.1 流程档案 一个流程档案就是一个zip文件,流程档案中 的核心文件是processdefinition.xml,该文件的主要信息是流程...

晨曦之光
2012/03/09
0
0
Java虚拟机

一、Java体系结构 Java虚拟机内部体系结构 1、Java虚拟机简介: Java虚拟机的主要任务是装载class文件并执行其中的字节码。Java虚拟机包含一个类装载器(class loader),它可以从程序和API...

whc20011
2016/08/26
51
0
AndroidLinker与SO加壳技术

Android 系统安全愈发重要,像传统pc安全的可执行文件加固一样,应用加固是Android系统安全中非常重要的一环。目前Android 应用加固可以分为dex加固和Native加固,Native 加固的保护对象为 ...

御安全
2016/11/18
270
0
深入理解JVM内幕

  每个Java开发者都知道Java字节码是执行在JRE((Java Runtime Environment Java运行时环境)上的。JRE中最重要的部分是Java虚拟机(JVM),JVM负责分析和执行Java字节码。Java开发人员并不...

john_ke
2016/09/27
21
0
Java字节码工具--Jiapi

Jiapi是一个用来改变正常Class装载过程的工具。Jiapi通过操作类的字节码来代替Class原来被装载的过程。被操作的Class传给一个可 以把Class装载到Java虚拟机的类装载器。通过Jiapi工具被编译的...

匿名
2009/06/18
841
0
深入理解JVM内幕:从基本结构到Java 7新特

摘要:许多没有深入理解JVM的开发者也开发出了很多非常好的应用和类库。不过,如果你更加理解JVM的话,你就会更加理解Java,这样你会有助于你处理类似于我们前面的案例中的问题。 每个Java开...

开源中国驻成都办事处
2012/12/06
0
1
类加载器ClassLoader-2

类装载器 大纲: n class装载验证流程 n 什么是类装载器ClassLoader n JDK中ClassLoader默认设计模式 n 打破常规模式 n 热替换 class装载验证流程: n 加载 n 链接 – 验证 – 准备 – 解析 ...

康熙兄弟
06/02
0
0
java的反射和它的类加载机制

java 的类装载系统: 在java虚拟机中有两种类装载器: 启动类装载器 和 自定义类装载器。 前者是jvm的一部分,后者是java程序的一部分。不同的类装载器放在不懂得命名空间中。 类转载子系统涉...

Richard_sun
2013/01/18
0
0
Java基础知识——类装载器与反射机制

类装载器ClassLoader 类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。 类装载器把一个类装入JVM中,要经过三步: 1.装载:查找和导入Class文件; 2.链接:执行校验、准...

札小白
2014/06/21
0
0
java类加载机制与反射

java 的类装载系统: 在java虚拟机中有两种类装载器: 启动类装载器 和 自定义类装载器。 前者是jvm的一部分,后者是java程序的一部分。不同的类装载器放在不懂得命名空间中。 类转载子系统涉...

e良师益友
2016/01/14
60
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

idea tomcat 远程调试

tomcat 配置 编辑文件${tomcat_home}/bin/catalina.sh,在文件开头添加如下代码。    CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7829" Idea端配......

qwfys
今天
1
0
遍历目录下的文件每250M打包一个文件

#!/usr/bin/env python # -*- utf-8 -*- # @Time : 2018/7/20 0020 下午 10:16 # @Author : 陈元 # @Email : abcmeabc@163.com # @file : tarFile.py import os import tarfile import thr......

寻爱的小草
今天
1
0
expect同步文件&expect指定host和要同步的文件&构建文件分发系统&批量远程执行命令

20.31 expect脚本同步文件 expect通过与rsync结合,可以在一台机器上把文件自动同步到多台机器上 编写脚本 [root@linux-5 ~]# cd /usr/local/sbin[root@linux-5 sbin]# vim 4.expect#!/...

影夜Linux
今天
1
0
SpringBoot | 第九章:Mybatis-plus的集成和使用

前言 本章节开始介绍数据访问方面的相关知识点。对于后端开发者而言,和数据库打交道是每天都在进行的,所以一个好用的ORM框架是很有必要的。目前,绝大部分公司都选择MyBatis框架作为底层数...

oKong
今天
12
0
win10 上安装解压版mysql

1.效果 2. 下载MySQL 压缩版 下载地址: https://downloads.mysql.com/archives/community/ 3. 配置 3.1 将下载的文件解压到合适的位置 我最终将myql文件 放在:D:\develop\mysql 最终放的位...

Lucky_Me
今天
2
0
linux服务器修改mtu值优化cpu

一、jumbo frames 相关 1、什么是jumbo frames Jumbo frames 是指比标准Ethernet Frames长的frame,即比1518/1522 bit大的frames,Jumbo frame的大小是每个设备厂商规定的,不属于IEEE标准;...

问题终结者
今天
2
0
expect脚本同步文件expect脚本指定host和要同步的文件 构建文件分发系统批量远程执行命令

expect脚本同步文件 在一台机器上把文件同步到多台机器上 自动同步文件 vim 4.expect [root@yong-01 sbin]# vim 4.expect#!/usr/bin/expectset passwd "20655739"spawn rsync -av ro...

lyy549745
今天
1
0
36.rsync下 日志 screen

10.32/10.33 rsync通过服务同步 10.34 linux系统日志 10.35 screen工具 10.32/10.33 rsync通过服务同步: rsync还可以通过服务的方式同步。那需要开启一个服务,他的架构是cs架构,客户端服务...

王鑫linux
今天
1
0
matplotlib 保存图片时的参数

简单绘图 import matplotlib.pyplot as pltplt.plot(range(10)) 保存为csv格式,放大后依然很清晰 plt.savefig('t1.svg') 普通保存放大后会有点模糊文件大小20多k plt.savefig('t5.p...

阿豪boy
今天
3
0
java 8 复合Lambda 表达式

comparator 比较器复合 //排序Comparator.comparing(Apple::getWeight);List<Apple> list = Stream.of(new Apple(1, "a"), new Apple(2, "b"), new Apple(3, "c")) .collect(......

Canaan_
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部