1.JVM概述

1.1 虚拟机

  • 所谓虚拟机(Virtual Machine),就是一台虚拟的计算机。它是一款软件,用来执 行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。
  • 大名鼎鼎的 VMware 就属于系统虚拟机,它是完全对物理计算机的仿真,提供了一 个可运行完整操作系统的软件平台。程序虚拟机典型的代表就是 java 虚拟机了,它专门为 执行某个单个计算机程序而设计。在 java 虚拟机中执行的指令我们称为 java 字节码指令。
  • Java 虚拟机是一种执行 java 字节码文件的虚拟计算机,它拥有独立的运行机制。
  • Java 技术的核心就是 java 虚拟机,因为所有的 java 程序都运行在 java 虚拟机内部

1.2 JVM作用

命令显示java vm的参数设置 .java.vm_java

Java 虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对 应平台上的机器码指令执行,每一条 java 指令,java 虚拟机中都有详细定义,如怎么取操 作数,怎么处理操作数,处理结果放在哪儿.

特点

  • 一次编译到处运行
  • 自动内存管理
  • 自动垃圾回收功能

现在的 JVM 不仅可以执行 java 字节码文件,还可以执行其他语言编译后的字节码文件,是一 个跨语言平台

命令显示java vm的参数设置 .java.vm_开发语言_02

1.3 JVM的位置

JVM是运行在操作系统之上的,它与硬件没有直接的交互.

命令显示java vm的参数设置 .java.vm_jvm_03

命令显示java vm的参数设置 .java.vm_java_04

1.4 JVM组成的四个部分

1.类加载器(ClassLoader)

2.运行时数据区(Runtime Data Area)

3.执行引擎(Execution Engine)

4.本地库接口(Native Interface)

1.5 各个组成部分的用途

  • 程序在执行之前先要把 java 代码转换成字节码(class 件)jvm 首先需要 把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中 的运行时数据区(Runtime Data Area) ,而字节码文件是 jvm 的一套指 令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析 器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由 CPU 去执行,而这个过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能,这就是这 4 个主要组成部分的职责与 功能。
  • 而我们通常所说的 JVM 组成指的是运行时数据区(Runtime Data Area),因为通常需要程序员调试分析的区域就是“运行时数据区”,或者 更具体的来说就是“运行时数据区”里面的 Heap(堆)模块

1.6 Java代码的执行流程

命令显示java vm的参数设置 .java.vm_面试_05


命令显示java vm的参数设置 .java.vm_jvm_06

  • Java 编译器编译过程中,任何一个节点执行失败就会造成编译失败。虽然各个 平台的 java 虚拟机内部实现细节不尽相同,但是它们执行的字节码内容却是一 样的。
  • JVM 主要任务就是负责将字节码装载到其内部,解释/编译为对应平台上的机器 指令执行。JVM 使用类加载器(Class Loader)装载 class 文件。 类加载完成后,会进行字节码校验,字节码校验通过之后 JVM 解释器会把字节 码翻译成机器码交由操作系统执行。 但不是所有的代码都是解释执行,JVM 对此作了优化,比如 HotSpot 虚拟机, 它本身提供了 JIT(Just In Time)编译器

1.7 JVM架构模型

Java 编译器输入的指令流基本上是一种基于栈的指令集架构,另一种指令集架构 是基于寄存器的指令集架构

这两种架构之间的区别:

基于栈式架构的特点:

  • 设计和实现更简单,适用于资源受限的系统
  • 使用零地址指令方式分配,其执行过程依赖于操作栈,指令集更小,编译器容易实现.
  • 不需要硬件支持,可移植性好,更好实现跨平台

基于寄存器式架构特点:

  • 指令完全依赖于硬件,可移植性差.
  • 性能优秀,执行更高效.
  • 完成一项操作使用的指令更少.

2. JVM结构-类加载

2.1 类加载子系统

命令显示java vm的参数设置 .java.vm_jvm_07

类加载器子系统负责从文件系统或者网络中加载 class 文件classLoader 只 负责 class 文件的加载,至于它是否可以运行,则由 Execution Engine 决定。 加载的类信息存放于一块称为方法区(元空间)的内存空间

2.2类加载的角色

命令显示java vm的参数设置 .java.vm_jvm_08

  1. class file 存在于硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板 在执行的时候是要加载 JVM 当中来,根据这个模板实例化出 n 个一模一样的实 例.
  2. class file 加载到 JVM 中,被称为 DNA 元数据模板,放在方法区中.
  3. 在.class–>JVM–>最终称为元数据模板,此过程就要有一个运输工具(类加 载器 Class Loader),扮演一个快递员

2.3类加载过程

命令显示java vm的参数设置 .java.vm_命令显示java vm的参数设置_09

2.3.1 加载

  1. 通过类名(地址)获取此类的二进制字节流.
  2. 将这个字节流所代表的静态存储结构转换为方法区(元空间)的运行时结构.
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为这个类的各种数 据的访问入口

2.3.2 链接

验证: 验证字节码文件格式是否是当前虚拟机所支持的文件格式,语法格式

准备: 为静态成员分配默认值(int 默认值0) 注意static final在编译期间赋值

解析: 将字节码中符号引用 替换 成 直接引用

例如: 编写代码 方法1 中调用 方法2 (符号引用)

类加载到内存后把符号的引用地址 换成 内存的地址引用

2.3.3 初始化

什么时候初始化?

1 )创建类的实例,也就是 new 一个对象

2)访问某个类或接口的静态变量,或者对该静态变量赋值

3)调用类的静态方法

4)反射(Class.forName(“”))

5)初始化一个类的子类(会首先初始化子类的父类)

类的初始化顺序

对 static 修饰的变量或语句块进行赋值. 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。 顺序是:父类 static –> 子类 static –> 父类构造方法- -> 子类构造

2.4 类加载器的分类

站在JVM的角度看,类加载器可以分为两种:

1.引导类加载器(启动类加载器)

2.其他所有类加载器,这些类加载器由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类.

站在java开发人员角度来看,类加载器就应当划分的更加细致一些.自JDK1.2以来Java一直保持着三层类加载器

命令显示java vm的参数设置 .java.vm_命令显示java vm的参数设置_10

2.4.1 引导类加载器(启动类加载BootStrap ClassLoader)

这个类加载器使用 C/C++语言实现,嵌套在 JVM 内部.它用来加载 java 核心类 库.并不继承于 java.lang.ClassLoader 没有父加载器. 负责加载扩展类加载器和应用类加载器,并为他们指定父类加载器. 出于安全考虑,引用类加载器只加载存放在\lib 目录,或者被 -Xbootclasspath 参数锁指定的路径中存储放的类

2.4.2 扩展类加载器(ExtensionClassLoader)

Java 语言编写的,由 sun.misc.Launcher$ExtClassLoader 实现. 派生于 ClassLoader 类. 从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 系统安装目录的 jre/lib/ext 子目录(扩展目录)下加载类库.如果用户创建的 jar 放在此目录下,也 会自动由扩展类加载器加载.

2.4.3 应用程序类加载器(系统类加载器 Application ClassLoader)

Java 语言编写的,由 sun.misc.Launcher$AppClassLoader 实现. 派生于 ClassLoader 类. 加载我们自己定义的类,用于加载用户类路径(classpath)上所有的类. 该类加载器是程序中默认的类加载器.

命令显示java vm的参数设置 .java.vm_jvm_11

ClassLoader 类,它是一个抽象类,其后所有的类加载器都继承自 ClassLoader (不包括启动类加载器

2.5双亲委派机制

Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要该类时才会 将它的 class 文件加载到内存中生成 class 对象.而且加载某个类的 class 文件 时,Java 虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委 派模式.

在这里插入图片描述

工作原理:

  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请 求委托给父类的加载器去执行.
  2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终 将到达顶层的启动类加载器.
  3. 如果父类加载器可以完成类的加载任务,就成功返回,倘若父类加载器无法完 成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制.

如果均加载失败,就会抛出 ClassNotFoundException 异常。

双亲委派的优点:

1 安全,可避免用户自己编写的类动态替换 Java 的核心类,如 java.lang.String

2 避免全限定命名的类重复加载(使用了 findLoadClass()判断当前类是否已加 载

2.6类的主动使用/被动使用

JVM规定,每个类或者接口被首次主动使用时才对其进行初始化,有主动使用就有被动使用.

主动使用:

  • 通过new关键字被导致类的初始化,这是大家经常使用的初始化一个类的方式, 他肯定会导致类的加载并且初始化
  • 访问类的静态变量,包括读取和更新
  • 访问类的静态方法
  • 对某个类进行反射操作,会导致类的初始化
  • 初始化子类会导致父类的的初始化
  • 执行该类的 main 函

被动使用:

其实除了上面的几种主动使用其余就是被动使用了 1.引用该类的静态常量,注意是常量,不会导致初始化,但是也有意外,这里的常量 是指已经指定字面量的常量,对于那些需要一些计算才能得出结果的常量就会导 致初始化,比如: public final static int NUMBER = 5 ; //不会导致类初始化,被动使用 public final static int RANDOM = new Random().nextInt() ; //会导致类的初 始化,主动使用

2.构造某个类的数组时不会导致该类的初始化,比如: Student[] students = new Student[10] ;

主动使用和被动使用的区别在于类是否会被初始化