1.jvm概述
1.1虚拟机
虚拟机(Virtual Machine),是一款软件,用来执行一系列虚拟计算机指令,大体上,虚拟机可以分为系统虚拟机和程序虚拟机。
VMware 就属于系统虚拟机,它是完全对物理计算机的仿真,提供了一 个可运行完整操作系统的软件平台。程序虚拟机典型的代表就是 java 虚拟机了,它专门为 执行某个单个计算机程序而设计。在 java 虚拟机中执行的指令我们称为 java 字节码指令。
Java 虚拟机是一种执行 java 字节码文件的虚拟计算机,它拥有独立的运行机制。
Java 技术的核心就是 java 虚拟机,因为所有的 java 程序都运行在 java 虚拟机内部。
1.2jvm作用
Java 虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对
应平台上的机器码指令执行.
java中的编译器和解释器:
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机
器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序
只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转
换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即
扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的
解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成
字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,
解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提
到的Java的特点的编译与解释并存的解释。
特点
- 一次编译到处运行
- 自动内存管理
- 自动垃圾回收机制
现在的 JVM 不仅可以执行 java 字节码文件,还可以执行其他语言编译后的字节码文件,是一
个跨语言平台
1.3jvm的位置
.java文件先被Java编译器编译成.class文件
1.5各个组成部分的用途
程序在执行之前先要把 java 代码转换成字节码(class 文件),jvm 首先需
要把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中 运
行时数据区(Runtime Data Area),而字节码文件是 jvm 的一套指令集规范,
并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎
(Execution Engine) 将字节码翻译成底层系统指令再交由 CPU 去执行,而
这个过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现
整个程序的功能,这就是这 4 个主要组成部分的职责与功能。
而我们通常所说的 JVM 组成指的是运行时数据区(Runtime Data Area),因
为通常需要程序员调试分析的区域就是“运行时数据区”,或者更具体的来说就
是“运行时数据区”里面的 Heap(堆)模块,那接下来我们来看运行时数据区
(Runtime Data Area)是由哪些模块组成的
1.6java代码的执行流程
虽然各个平台的 java虚拟机内部实现细节不尽相同,但是它们执行的字节码内容却是一样的。
JVM 主要任务就是负责将字节码装载到其内部,解释/编译为对应平台上的机器指令执行。
JVM 使用类加载器(Class Loader)装载 class 文件。
类加载完成后,会进行字节码校验,字节码校验通过之后 JVM 解释器会把字节码翻译成机
器码交由操作系统执行。
但不是所有的代码都是解释执行,JVM 对此作了优化,比如 HotSpot 虚拟机,它本身提供
了 JIT(Just In Time)
1.7JVM架构模型
Java 编译器输入的指令流基本上是一种基于栈的指令集架构,另一种指令集架构是基于寄存
器的指令集架构.
基于栈式架构的特点
设计和实现更简单,适用于资源受限的系统.
使用零地址指令方式分配,其执行过程依赖于操作栈,指令集更小,编译器容易实现.
不需要硬件支持,可移植性好,更好实现跨平台.
基于寄存器式架构特点:
指令完全依赖于硬件,可移植性差.
性能优秀,执行更高效.
完成一项操作使用的指令更少.
所以由于跨平台的设计,Java指令集都是根据栈来设计的,不同CPU架构不同,所以不能设计
为基于寄存器的.
优点是跨平台,指令集小,编译器容易实现.
缺点是性能下降,实现同样功能需要更多的指令.
2.jvm结构-类加载
2.1类加载子系统的作用
classLoader 只负责 class 文件的加载,至于它是否可以运行,则由 Execution
Engine 决定。加载的类信息存放于一块称为方法区的内存空间。除了类的信息
外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量
(这部分常量信息是 class 文件中常量池部分的内存映射).
2.2类加载ClassLoader的角色
.
在.class–>JVM–>最终称为元数据模板,此过程就要有一个运输工具(类加
载器 Class Loader),扮演一个快递员的角色.
2.3类加载过程
2.3.1加载
-
通过类名(地址)获取此类的二进制字节流.
2.将这个字节流所代表的静态存储结构转换为方法去的运行时结构.
3.在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的
各种数据的访问入口.
2.3.2链接:
-
验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致;
-
准备:准备阶段则负责为类的静态属性分配内存,并设置默认初始值
不包含用 final 修饰的 static,实例变量
-
解析:将类的二进制数据中的符号引用替换成直接引用(符号引用是用一组符
号描述所引用的目标;直接引用是指向目标的指针).
2.3.3初始化:
类什么时候初始化?
1 )创建类的实例,也就是 new 一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName(“”))
5)初始化一个类的子类(会首先初始化子类的父类)
类的初始化顺序
对 static 修饰的变量或语句块进行赋值.
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
顺序是:父类 static –> 子类 static –> 父类构造方法- -> 子类构造方法
2.4类加载器分类
JVM 支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)
和自定义类加载器(User-Defined ClassLoader).
从概念上来讲,自定义类加载器一般指的是程序汇总由开发人员自定义的一类加
载器,但是 Java 虚拟机规范却没有这么定义 ,而是将所有派生于抽象类
ClassLoader 的类加载器都划分为自定义类加载器.
无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有 3 个:
2.4.1引导类加载器(启动类加载器 BootStrap ClassLoader))
这个类加载器使用 C/C++语言实现,嵌套在 JVM 内部.它用来加载 java 核心类
库.
并不集成于 java.lang.ClassLoader 没有父加载器.
负责加载扩展类加载器和应用类加载器,并为他们指定父类加载器.
出于安全考虑,引用类加载器只加载包名为 java,javax,sun 等开头的类.
2.4.2扩展类加载器**(Extension ClassLoader**)
上层类加载器为引用类加载器.
从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 系统安装目录的
jre/lib/ext 子目录(扩展目录)下加载类库.如果用户创建的 jar 放在此目录下,也
会自动由扩展类加载器加载
2.4.3 应用程序类加载器(系统类加载器 器 Application ClassLoader)
派生于 ClassLoader 类.
上层类加载器为扩展类加载器.
加载我们自己定义的类.
该类加载器是程序中默认的类加载器
2.5双亲委派机制
Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要该类时才会
将它的 class 文件加载到内存中生成 class 对象.而且加载某个类的 class 文件
时,Java 虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委
派模式
工作原理:
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请
求委托给父类的加载器去执行.
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终
将到达顶层的启动类加载器.
- 如果父类加载器可以完成类的加载任务,就成功返回,倘若父类加载器无法完
成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制.
如果均加载失败,就会抛出 ClassNotFoundException 异常。
双亲委派优点?
1 安全,可避免用户自己编写的类动态替换 Java 的核心类,如 java.lang.String
2 避免全限定命名的类重复加载(使用了 findLoadClass()判断当前类是否已加
载)
2.6沙箱安全机制
作用**😗*防止恶意代码污染 java 源代码
比如上面我们定义了一个类名为 String 所在包也命名为 java.lang,因为这个类
本来是属于 jdk 的,如果没有沙箱安全机制的话,这个类将会污染到系统中的
String,但是由于沙箱安全机制,所以就委托顶层的引导类加载器查找这个类,如
果没有的话就委托给扩展类加载器,再没有就委托到系统类加载器.但是由于
String 就是 jdk 的源代码,所以在引导类加载器那里就加载到了,先找到先使用,
所以就使用引导类加载器里面的 String,后面的一概不能使用,这就保证了不被
恶意代码污染
面试题:
在 jvm 中如何判断两个对象是属于同一个类?
-
类的全类名(地址)完全一致.
-
类的加载器必须相同.
2.7 类的主动使用/被动使用
JVM 规定,每个类或者接口被首次主动使用时才对齐进行初始化,有主动使用,自
然就有被动使用.
主动使用:
1.通过 new 关键字被导致类的初始化,这是大家经常使用的初始化一个类的方式,
他肯定会导致类的加载并且初始化
2.访问类的静态变量,包括读取和更新
3.访问类的静态方法
4.对某个类进行反射操作,会导致类的初始化
5.初始化子类会导致父类的的初始化
6.执行该类的 main 函数
被动使用:
其实除了上面的 6 种主动使用就是被动使用了,但还是需要知道一些情况
1:引用该类的静态常量,注意是常量,不会导致初始化,但是也有意外,这里的常量
是指已经指定字面量的常量,对于那些需要一些计算才能得出结果的常量就会导
致初始化,比如:
public final static int NUMBER = 5 ; //不会导致类初始化,被动使用
public final static int RANDOM = new Random().nextInt() ; //会导致类的初
始化,主动使用
2:构造某个类的数组时不会导致该类的初始化,比如:
6.执行该类的 main 函数
被动使用:
其实除了上面的 6 种主动使用就是被动使用了,但还是需要知道一些情况
1:引用该类的静态常量,注意是常量,不会导致初始化,但是也有意外,这里的常量
是指已经指定字面量的常量,对于那些需要一些计算才能得出结果的常量就会导
致初始化,比如:
public final static int NUMBER = 5 ; //不会导致类初始化,被动使用
public final static int RANDOM = new Random().nextInt() ; //会导致类的初
始化,主动使用
2:构造某个类的数组时不会导致该类的初始化,比如:
Student[] students = new Student[10] ;