java 类加载 编译_JVM编译及类加载流程

与C和C++等语言不同,C和C++是通过编译器直接将代码编译成CPU能理解的代码格式,即机器码,然后执行。

Java为了实现跨平台运行,是将程序编程成Java字节码,将字节码交给JVM来运行,这样做的好处不仅是实现了跨平台,同时JVM还会提供一个Managed Runtime(托管环境),这个东东能够帮助我们处理自动内存管理、GC、数组越界、安全权限等检测,避免我们写这些无关业务逻辑的代码。

JVM如何运行字节码:

程序都是从main方法开始运行,然后再调用其他方法,而类信息、常量、静态变量等数据都被放在一个在JVM规范中被称为“方法区”的地方,HotSpot中被称为永久代(PermGen)。

需要注意的是JDK1.8之前,永久代是在JVM中的,但是JDK1.8中,使用元空间(Metaspace)替换了永久代,并且不在虚拟机中了,转而使用的是本地内存。这么做的目的一是防止方法区太小导致内存溢出,而是为了将HotSpot和JRockit融合,因为后者没有永久代。

在调用方法时,JVM会在当前线程的栈中生成一个栈帧,用来存放局部变量以及字节码的操作数,并且这个栈帧大小是提前计算好的,且不要求连续分布。当方法执行完毕之后,进行出栈操作。

6d552ba8fb45d2b5bbe4f2df92766645.png

HotSpot在执行时,是采用解释执行和即时编译混合执行的方法,前世逐条将字节码编译成机器码然后执行,后者是将所有的字节码都编译完了之后再执行。后者运行速度更快,但是需要等待编译。

HotSpot有多个即时编译器,默认采用分层编译的方法,热点代码首先被Client编译器编译,然后热点中的热点又会被Server编译器编译,理论上Java程序会执行的越来越快,实际嘛…至少暂时没啥感觉,可能因为我水平没有达到那个份上

JVM如何加载类的:

JVM的类加载指的是将类的.class文件中的二进制数据读入到内存,将其放在运行时数据区的方法区内,然后再堆区创建一个java.lang.Class对象。Class对象封装了类在方法区内的数据结构,并向程序眼提供了访问这些数据结构的接口。

类加载过程包括加载、验证、准备、解析和初始化,如下所示:

a62506cdc34729049ebe7ed994262a1b.png

加载:

通过类的全限定名来获取其定义的二进制字节流(一般是.class文件),生成一个Class对象并初始化它的数据结构(类加载器在后续进行说明)。

验证:

为了保证JVM的安全进行对Class文件的字节流进行一些验证,细节就不说了。

准备:

发生在方法区,为类的静态变量分配内存,并将其初始化为默认值,并构造与该类相关联的方法表,这个是实现动态绑定的关键。但是如果这个变量同时被final和static修饰,那就会初始化为它定义的值。

单例模式中静态内部类实现方法的依据就是这个。

解析:

把常量池中的符号引用转为直接引用,符号引用就是class文件中的:

CONSTANT_Class_info

CONSTANT_Field_info

CONSTANT_Method_info……

符号引用的目标不一定要已经加载到内存中,但是直接引用要求目标一定要在内存中,且能定位到目标(句柄)。如果符号引用指向一个未被加载的类或者它的字段或者方法,那么就会触发这个类的加载(但不一定会触发它加载之后的操作)。符号引用在Java编译器生成Class文件的时候,在文件中就已经指定好了。

初始化:

真正执行Java程序代码,主要是对类变量进行初始化。先初始化父类,再初始化子类:

1.声明类变量是指定初始值

2.使用静态代码块为类变量指定初始值

初始化阶段是执行类构造器()方法的过程,它是编译器自动生成的,并且会优先执行父类的()方法,并且它的线程安全性由JVM来保证。如果类中没有对静态变量赋值也没有静态代码块的话,可以不生成这个方法。

有且仅有5种情况必须立即初始化:(这些情况被称为类的主动引用)

1.程序启动,main()那个类必须先初始化

2.使用new、读取或者设置类的静态字段(被final修饰过的已在准备阶段赋值,不算)或者调用类静态方法的时候

3.如果子类的父类没有初始化,那么先初始化父类

4.使用反射的时候

5.JDK1.7中的动态语言支持会触发初始化,这部分不是特别明白

引用类方法但不会初始化类的被称为被动引用,有如下几种:

1.引用final static对象不会导致类的初始化

2.通过子类引用父类的静态字段,父类会初始化,子类不会

3.通过类名获取Class对象不会触发类的初始化

4.通过ClassLoader默认的loadClass方法也不会触发初始化

5.对应对象数组,不会触发初始化,即new [Class***]

……

需要注意的是,解析和初始化不一定会按顺序执行,这样做是为了支持Java的动态绑定。动态绑定(重写)是和静态绑定(重载)做区分。静态绑定的方法调用时,实际的引用时将指向具体的目标方法,因为重载方法的区分在编译阶段已经完成;而动态绑定时,JVM会根据调用者的实际类型,指向的是方法表中的内容,JVM会为每个类生产一张表,这张表叫方法表,通过查询这张表来确定实际调用的方法。

(这就是JVM进行动态绑定的实现原理,并且JVM会使用到一种叫内联缓存的技术,这是一种加快动态绑定的技术,可以理解为动态绑定的缓存)

类加载器:类加载阶段使用(双亲委派模型)

JVM提供了3种类加载器:

启动类加载器

加载JAVA_HOME/lib目录或者-Xbootclasspath指定路径且被JVM认可的类库

扩展类加载器

加载Java_HOME/lib/ext或者java.ext.dir指定路径中的所有类库,开发者可直接使用

应用程序类加载器

通过getSystemClassLoader获取,加载classpath中的类库

用户还可以自定义类加载器,如果没有,那默认使用“应用程序类加载器”

类加载器是这么工作的,当一个类加载器收到类加载任务时,会先交给其父类加载器完成,只有当父类无法完成加载任务时,才会尝试自己加载任务。这种层次模型被称为双亲委派模型。之所以这么做的原因是为了保证程序的安全运行,因为同一个类如果使用不同的加载器加载,得到的是不同的类。

adaf2e394cc856fd4654635436589e07.png

参考:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值