JVM 加载 class 文件的原理机制----------【Java基础】

问:你在写Java代码时,有没有对代码是如何转换为你想要的结果的。

答:这是eclipse中自带的JVM,使其编译成字节码文件;所以Java代码的运行过程也就是:编译源码文件---编译字节码文件---加载class文件---运行class字节码文件;之后的两个过程是在JVM中运行。

这里写图片描述

 

问:那你知道如何在cmd命令窗口编译Java文件吗?而这个过程等于eclipse的什么操作?

答:其实在命令窗口也就是输入:javac 文件名.java;这样就会生成三个.class文件,这三个文件分别为:两个类、一个接口;

而这个操作在eclipse中等于Ctr+s;

 

问:那你知道Java是如何实现跨平台的?

答:Java之所以能跨平台,很大一部分是得以字节码文件的;这是因为在不同的平台上都使用这种程序存储格式;进一步说就是只要是字节码文件就可以了;

 

问:那JVM可以实现跨语言吗?

答:额,这也是可以的;因为如果非常熟悉字节码的格式要求,就可以使用二进制编译器自己写一个符合要求的字节码文件,之后交给JVM运行即可,这也就说可以把其他语言编写的源码编译成字节码文件,然后交给JVM运行。

一个字节码文件:(参考)

这里写图片描述

解释:编译自Student.java,编译器的主版本号是52,也就是jdk1.8,这个类是public,然后是存放类中常量的常量池,各个方法的字节码等。它存放了这个类的各种信息:字段、方法、父类、实现的接口等各种信息。

 

问:那你知道如何在命令窗口中运行class文件吗?

答:其实也就是输入:java 文件名;这就会启动java虚拟机;加载该java的字节码文件到内存中,之后就是运行内存中的字节码指令了。

 

问:看来你对java的虚拟机比较了解,你能给我介绍下JVM的基本结构吗?

答:

JVM基本结构如下图所示:

这里写图片描述

JVM划分为五部分了;

其中的方法区是用于存放类、接口的元数据信息的,加载进来的字节码数据都存储在这里;之后也就到运行存放的字节码文件了;这个部分也就是java栈;它是执行引擎运行字节码时的运行时内存区,采用的是栈帧的形式保存每个方法的调用运行数据 。

剩余的三部分也就是一些需要注意的地方;

本地方法栈:执行引擎调用本地方法时的运行时内存区。
Java堆(堆):运行时数据区,各种对象一般都存储在堆上。
PC寄存器(程序计数器):功能如同CPU中的PC寄存器,指示要执行的字节码指令。

 

其实JVM的功能模块主要是:类加载器、执行引擎和垃圾回收系统。

 

问:看来你对JVM的基本结构了解的也不错,给我详细讲下类加载器!

答:其实在java中的所有类,都必须被装载到jvm中才能运行的;而装载这个工作就是由jvm的类加载器完成的;而加载这个的本质就是把类文件从硬件读取到内存中;其实类加载器也分种类,其有两种装载,一种为隐式装载,也就是在运行过程中碰到通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中。而另一种方式就是显示装载,就是通过class.forname()等方法,显示加载需要的类。

而类加载具有动态性,该性体现在一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再 
运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载。

之所以这样做的原因就是节省内存的开销;因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现 。

其实类加载器就是类,只是功能是把类加载入JVM中;而且类加载器分为三层结构,如下所示:

Bootstrap Loader  - 负责加载系统类 -----------JRE/lib/rt.jar
            | 
          - - ExtClassLoader  - 负责加载扩展类 -----------JRE/lib/ext或者java.ext.dirs指向的目录
                          | 
                      - - AppClassLoader  - 负责加载应用类------------CLASSPATH环境变量, 由-
                        classpath或-cp选项定义,或者是JAR中的Manifest的classpath属性定义.

至于为什么是三个类加载器,其实有两方面的原因;一方面是为了分工明确,另一方面也就是为了实现委托模型。

 

问:看来你对类加载器很是了解,那你知道三个类加载器之间是如何协调工作的吗?

答:其实这和类加载器的工作原理有一定的关系,因为其工作原理又是基于三个机制:委托、可见性和单一性;

第一个的委托机制也就是当一个类加载和初始化的时候,类仅在有需要加载的时候被加载。假设你有一个应用需要的类叫作Abc.class,首先加载这个类的请求由 Application类加载器委托给它的父类加载器Extension类加载器,然后再委托给Bootstrap类加载器。Bootstrap类加载器 会先看看rt.jar中有没有这个类,因为并没有这个类,所以这个请求由回到Extension类加载器,它会查看jre/lib/ext目录下有没有这 个类,如果这个类被Extension类加载器找到了,那么它将被加载,而Application类加载器不会加载这个类;而如果这个类没有被 Extension类加载器找到,那么再由Application类加载器从classpath中寻找。记住classpath定义的是类文件的加载目 录,而PATH是定义的是可执行程序如javac,java等的执行路径。

第二个可见性机制其实也就是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。

第三个单一性机制就是根据这个机制,父加载器加载过的类不能被子加载器加载第二次。虽然重写违反委托和单一性机制的类加载器是可能的,但这样做并不可取。

 

问:你能给我说下类加载过程吗?讲了那么多的类加载的知识。

答:类加载的话,也就分为三个步骤,如下图所示:

从上面的图看,就会有一定疑问?为什么有了初始化过程还要验证过程呢?这不就有点冲突吗?

其实如果由编译器生成的class文件,它肯定是符合JVM字节码格式的;但是万一有高手自己写一个class文件,让JVM加载并运行,用于恶意用途,就不妙了,因此这个class文件要先过验证这一关,不符合的话不会让它继续执行的,也是为了安全考虑吧。

这就是和Java安全性有关了。

而准备阶段和初始化阶段看似有点矛盾,其实不是的。因为如果类中有语句:private static int a = 10。-------那么它的执行过程是这样的:首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,再给a分配内存,又因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0;然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,所以此时a才等于10。

 

问:那么你知道类在什么时候会被初始化?

答:其实有很多种情况的:

1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName("com.lyj.load"))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM启动时标明的启动类,即文件名和类名相同的那个类
 

而且类的初始化也是分步骤的:

1)如果这个类还没有被加载和链接,那先进行加载和链接。
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)。
3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。

 

由于之前写的,当时忘记记下参考资料。

 

要是哪里写的地方不对,还望多多指教。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值