转载

你真的了解Jvm加载class文件吗?

在面试java工程师的时候,这道题经常被问到,故需特别注意。

认真阅读本文后,方可做到心中有数,面试谈笑风生。

你真的了解Jvm加载class文件吗?

1,JVM简介(简单了解)

JVM 全称是Java Virtual Machine ,Java 虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM 你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM 也是有这成套的元素,运算器是当然是交给硬件CPU 还处理了,只是为了适应“一次编译,随处运行”的情况,需要做一个翻译动作,于是就用了JVM 自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对一个系列的CPU ,比如8086 系列的汇编也是可以用在8088 上的,但是就不能跑在8051 上,而JVM 的命令集则是可以到处运行的,因为JVM 做了翻译,根据不同的CPU ,翻译成不同的机器语言。

JVM 中我们最需要深入理解的就是它的存储部分,存储?硬盘?NO ,NO , JVM 是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。

2,JVM组成(认真阅读)

你真的了解Jvm加载class文件吗?

该图参考了网上广为流传的JVM 构成图,大家看这个图,整个JVM 分为四部分:

Class Loader 类加载器

类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java 程序,然后通过javac 编译成class 文件,那怎么才能加载到内存中被执行呢?Class Loader 承担的就是这个责任,那不可能随便建立一个.class 文件就能被加载的,Class Loader 加载的class 文件是有格式要求,在《JVM Specification 》中式这样定义Class 文件的结构:

ClassFile {
      u4 magic;
      u2 minor_version;
       u2 major_version;
      u2 constant_pool_count;
      cp_info constant_pool[constant_pool_count-1];
      u2 access_flags;
      u2 this_class;
      u2 super_class;
      u2 interfaces_count;
      u2 interfaces[interfaces_count];
      u2 fields_count;
      field_info fields[fields_count];
      u2 methods_count;
      method_info methods[methods_count];
      u2 attributes_count;
      attribute_info attributes[attributes_count];
    }
复制代码

需要详细了解的话,可以仔细阅读《JVM Specification 》的第四章“The class File Format ”,这里不再详细说明。

友情提示:Class Loader 只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由Execution Engine 负责的。

Execution Engine 执行引擎

执行引擎也叫做解释器(Interpreter) ,负责解释命令,提交操作系统执行。

Native Interface 本地接口

本地接口的作用是融合不同的编程语言为Java 所用,它的初衷是融合C/C++ 程序,Java 诞生的时候是C/C++ 横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++ 程序,于是就在内存中专门开辟了一块区域处理标记为native 的代码,它的具体做法是Native Method Stack 中登记native 方法,在Execution Engine 执行时加载native libraies 。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java 程序驱动打印机,或者Java 系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket 通信,也可以使用Web Service 等等,不多做介绍。

Runtime data area 运行数据区

运行数据区是整个JVM 的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java 生态系统如此的繁荣,得益于该区域的优良自治。

整个JVM 框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!

3、JVM加载class文件的原理机制 (好好记住)

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

类装载方式,有两种

1. 隐式装载 , 程序在运行过程中当碰到通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中

2. 显式装载 , 通过class.forname()等方法,显式加载需要的类

隐式加载与显式加载的区别:两者本质是一样? Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

Java的类加载器有三个

对应Java的三种类:

1.系统类

2.扩展类

3.由程序员自定义的类

Bootstrap Loader  // 负责加载系统类 (指的是内置类,像是String,对应于C#中的System类和C/C++标准库中的类)
        | 
      - - ExtClassLoader   // 负责加载扩展类(就是继承类和实现类)
                      | 
                  - - AppClassLoader   // 负责加载应用类(程序员自定义的类)
复制代码

三个加载器各自完成自己的工作,但它们是如何协调工作呢?哪一个类该由哪个类加载器完成呢?为了解决这个问题,Java采用了委托模型机制。

委托模型机制的工作原理很简单:当类加载器需要加载类的时候,先请示其Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的搜索路径搜索该类。这样的顺序其实就是加载器层次上自顶而下的搜索,因为加载器必须保证基础类的加载。之所以是这种机制,还有一个安全上的考虑:如果某人将一个恶意的基础类加载到jvm,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。

我们可以通过这样的代码来获取类加载器:

ClassLoader loader = ClassName.class.getClassLoader();
ClassLoader ParentLoader = loader.getParent();
复制代码

注意一个很重要的问题,就是Java在逻辑上并不存在BootstrapKLoader的实体!因为它是用C++编写的,所以打印其内容将会得到null。

前面是对类加载器的简单介绍,它的原理机制非常简单,就是下面几个步骤:

1. 装载 :查找和导入class文件;

2.连接:

(1)检查:检查载入的class文件数据的正确性;

  (2)准备:为类的静态变量分配存储空间;

  (3)解析:将符号引用转换成直接引用(这一步是可选的)
复制代码

3. 初始化 :初始化静态变量,静态代码块。

这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。

稍微详细说下,加载步骤

装载

简单描述:在Java程序运行之前JVM会把编译完成的.class二进制文件加载到内存,后续提供程序使用,用到的就是类加载器ClassLoader 。加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未结束,连接阶段就可能开始了。但是夹在加载阶段进行的动作,仍然属于连接阶段的内容。

连接

  • 连接 - 验证

    验证是连接的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危及虚拟机本身的安全。 验证阶段的四个步骤:文件格式检验、元数据检验、字节码检验、符号引用检验。

文件格式检验:检验字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。

元数据检验:对字节码描述的信息进行语义分析,以保证其描述的内容符合Java语言规范的要求.

字节码检验:通过数据流和控制流分析,确定程序语义是合法、符合逻辑的。

符号引用检验:符号引用检验可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
复制代码
  • 连接 - 准备

    该阶段正式为类变量分配内存并设置类变量初始值。这些变量所使用的内存将在方法区中进行分配。此时进行内存分配的仅包括类变量,而不包括实例变量(实例变量将会在对象实例化时随着对象一起分配在Java堆中)。另外,在这里分配的静态类变量是将其值定义为默认值。因为在该阶段并未执行任何Java方法,正确的赋值将在初始化阶段执行。

  • 连接 - 解析

    该阶段虚拟机会将常量池内的符号引用替换为直接引用的过程。

初始化

这是类加载的最后一步,真正执行类中定义的字节码,也就是.class文件。 初始化阶段是执行类构造器方法的过程,以及真正初始化类变量和其他资源的过程。

你真的了解Jvm加载class文件吗?
原文  https://juejin.im/post/5dc7f6ff51882531677928f2
正文到此结束
Loading...