JVM原理及流程

本文参考链接1
本文参考链接2

目录


简述


  • 首先我们看一下这一段描述

说起java,首先想起的是一门编程语言,然而事实上,Java是一种技术,它由四方面组成:Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)。

  • 很显然,我们可以得到,java并不只是代表一门编程语言,编程语言只是它的一部分而已,而这里就将着重介绍java的另外一部分“java虚拟机”,也就是JVM。
  • 我们先来看一下java各部分之间的关系吧
    这里写图片描述

  • 我们可以看出,Java虚拟机(JVM)处在核心的位置,是程序与底层操作系统和硬件的关键。它的下方是移植接口,移植接口由两部分组成:适配器和java操作系统,其中依赖于平台的部分称为适配器;JVM通过移植接口在具体的平台和操作系统上实现;在JVM上方时java的基本类库和扩展类库以及他们的API,利用Java API编写的应用程序和小程序可以在任何Java平台上运行而无需考虑底层平台,就是因为Java虚拟机实现了程序与操作系统的分离,从而实现了Java的平台无关性。

  • JVM在他的生命周期中有一个明确的任务,那就是运行Java程序,因此当Java程序启动的时候,就产生JVM的一个实例;当程序运行结束的时候,该实例也跟着消失,下面我们从JVM的体系结构和它的运行过程这两个方面来对他进行比较深入的研究。

JVM的体系结构


  • 每个JVM都有两种机制:
    • 类装载子系统:装载具有适合名称的类或接口
    • 执行引擎:负责执行包含在已装载的类或接口中的指令
  • 每个JVM都包含:方法区、Java堆、Java栈、本地方法栈、指令计数器及其他隐含寄存器
    这里写图片描述
  • 对于JVM的学习,有几个比较重要的部分:
    • Java代码编译和执行的整个过程
    • JVM内存管理及垃圾回收机制

Java代码编译和执行的整个过程

  • Java代码编译和执行的整个过程大概是:开发## 标题 ##人员编写Java代码,然后将之编译成字节码,再然后字节码被装入内存,一旦字节码进入虚拟机,他就会被解释器解释执行,或者被即时代码发生器有选择的转换成机器码在执行
  • Java代码编译是由Java源码编译器来完成,也就是Java代码到JVM字节码(.class文件)的过程。 流程图如下所示:
    这里写图片描述
  • Java字节码的执行是由JVM执行引擎来完成,流程图如下所示:
    这里写图片描述
  • Java代码编译和执行的整个过程包含了以下三个重要的机制:
    • Java源码编译机制
    • 类加载机制
    • 类执行机制

Java源码编译机制

Java 源码编译由以下三个过程组成:

①分析和输入到符号表

②注解处理

③语义分析和生成class文件
最后生成的class文件由以下部分组成:

①结构信息:包括class文件格式版本号及各部分的数量与大小的信息

②元数据:对应于Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池

③方法信息:对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息

类加载机制

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:
这里写图片描述
ClassLoader加载流程:当运行一个程序的时候,JVM启动,运行bootstrap classloader,该ClassLoader加载Java核心API(ExtClassLoader和AppClasLoader也在此时被加载),然后调用ExtClassLoader加载拓展API,最终AppClasLoader

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

类执行机制

JVM是基于堆栈的虚拟机。JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。

JVM执行class字节码,线程创建后,都会产生程序计数器(PC)和栈(Stack),程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是有局部变量区和操作数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果。栈的结构如下图所示:
这里写图片描述

JVM内存管理及垃圾回收机制


内存模型

  • 首先介绍一下内存模型
  • 运行时数据区,即jvm内存结构图如下图
    这里写图片描述

    接下来我们来挨个介绍一下这里面的各个部分

  • a) 程序计数器(PC寄存器)
    当前线程所执行的字节码的行号指示器。由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切 换 之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。
  • b) java栈
    Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。 
  • c)本地方法栈
    本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的
  • d)堆
    Java中的堆是用来存储对象本身的以及数组(数组引用是存放在Java栈中的)。堆是被所有线程共享的,在JVM中只有一个堆。
  • e)方法区
    与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。
    这里写图片描述
    这里介绍一下两种访问对象的方式:

  • 使用句柄访问:
    这里写图片描述

  • 直接指针访问:
    这里写图片描述

垃圾回收机制

  • 收集并删除未引用的对象。可以通过调用”System.gc()”来触发垃圾回收,但并不保证会确实进行垃圾回收。JVM的垃圾回收只收集哪些由new关键字创建的对象。所以,如果不是用new创建的对象,你可以使用finalize函数来执行清理。
  • 垃圾回收器负责:
    • 分配内存
    • 保证所有正在被引用的对象还存在于内存中
    • 回收执行代码已经不再引用的对象所占的内存

年轻代(Young Generation)

○ Java应用在分配Java对象时,这些对象会被分配到年轻代堆空间中去

○ 这个空间大多是小对象并且会被频繁回收

○ 由于年轻代堆空间的垃圾回收会很频繁,因此其垃圾回收算法会更加重视回收效率

年老代(Old Generationn)

○ 年轻代堆空间的长期存活对象会转移到(也许是永久性转移)年老代堆空间

○ 这个堆空间通常比年轻代的堆空间大,并且其空间增长速度较缓

○ 由于大部分JVM堆空间都分配给了年老代,因此其垃圾回收算法需要更节省空间,此算法需要能够处理低垃圾密度的堆空间

持久代(Permanent Generation)-方法区

○ 存放VM和Java类的元数据(metadata),以及interned字符串和类的静态变量

猜你喜欢

转载自blog.csdn.net/qq_32038679/article/details/80562044
今日推荐