文章目录了解JVM(Java虚拟机)首先我们必须了解VM(虚拟机)是什么。 所谓虚拟机(Virtual Machine),就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。 我们经常见到的 VMware 就属于系统虚拟机,它是完全对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。 程序虚拟机典型的代表就是 Java 虚拟机了,它专门为执行某个单个计算机程序而设计。在 Java 虚拟机中执行的指令我们称为 Java 字节码指令。无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源。Java 虚拟机是一种执行 Java 字节码文件的虚拟计算机,它拥有独立的运行机制。 Java 技术的核心就是 Java 虚拟机,因为所有的 Java 程序都运行在 Java 虚拟机内部。 JVM概述JVM就是Java虚拟机,虚拟机就是一台虚拟的计算机,是一款软件。Java 虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释或编译为对应平台上的机器码指令执行,每一条 Java 指令,Java 虚拟机中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪儿等。JVM是运行在操作系统上的,不与硬件直接交互。 JVM整体的四个部分JVM整体组成可以分为4个部分:
程序在执行之前先要把 Java 代码转换成字节码(.class 文件),JVM 首先需要把字节码通过一定的类加载器(Class Loader)把文件加载到内存中运行时数据区(Runtime Data Area),而字节码文件是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器(执行引擎(Execution Engine)) 将字节码翻译成底层系统指令再交由 CPU 去执行,而这个过程中需要调用其他语言的接口(本地方法接口(Native Interface)) 来实现。整个程序的功能,这就是这 4 个主要组成部分的职责与功能。 1.1 类加载器
1.1.1 类加载器过程类加载器子系统负责从文件系统或者网络中加载 class 文件, class 文件在文件开头有特定的文件标识(字节码文件都以 CA FE BA BE 标识开头)。classLoader 只负责 class 文件的加载,至于它是否可以运行,则由执行引擎决定。加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是 class 文件中常量池部分的内存映射)。 类加载的过程 1.加载过程是把class文件(字节码文件)加载到内存中(I/O读写)。类加载器把文件加载到内存中,会为每个类创建一个Class类的对象,调用Class类中的方法获取类的相关信息。 2.验证是检验加载的类是否有正确的内部结构并和其他类协调一致。 3.准备阶段为类的静态属性分配内存,并设置默认初始值(不包含final修饰的static常量),也不会给实例变量初始化。 4.解析是将二进制数据中的符号引用替换成直接引用(符号引用是用一组符号描述所引用的目标;直接引用是指向目标的指针)。 5.类初始化 5.1 什么时候初始化类 1 )创建类的实例,也就是 new 一个对象 5.2 类的初始化顺序: 父类static --> 子类static --> 父类构造方法 --> 子类构造方法 1.1.2类加载器的分类1.1.2.1启动类加载器(引导类加载器)是由c/c++实现,用来加载Java的核心类库 1.1.2.2扩展类加载器由Java实现,派生于 ClassLoader 类。上层类加载器是引导类加载器(启动类加载器),加载底层类库 1.1.2.3应用程序类加载器Java实现,派生于 ClassLoader 类。上层类加载器是扩展类加载器,加载自定义类。 1.1.3 双亲委派机制加载类时,向上委派,交给最上层的加载器启动类加载器加载核心类库,加载不到就用扩展类加载器加载底层类库,加载不到就用应用程序类加载器加载自定义类 双亲委派机制的优点
1.1.4沙箱安全机制作用是防止恶意代码污染 Java 源代码 如果一个类在引导类加载器那里就加载到了,先找到先使用,所以就使用引导类加载器里面的类,后面的一概不能使用,这就保证了不被恶意代码污染。 1.1.5 类的主动使用和被动使用JVM 规定,每个类或者接口被首次主动使用时才对其进行初始化,有主动使用,自然就有被动使用。
被动使用:其实除了上面的几种主动使用其余就是被动使用了 注意:主动使用和被动使用的区别在于类是否会被初始化.
1.2 运行时数据区JVM 的运行时数据区,不同虚拟机实现可能略微有所不同,但都会遵从 Java 虚拟机规范,Java 8 虚拟机规范规定,Java 虚拟机所管理的内存将会包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。 1.2.1 程序计数器(Program Counter Register)JVM 中的程序计数寄存器中的 Register 命名源于CPU 的寄存器,寄存器存储指令相关的现场信息。这里,并非是广义上所指的物理寄存器,或许将其翻译为 PC 计数器(或指令计数器)会更加贴切(也称为程序钩子)。JVM 中的PC 寄存器是对物理 PC 寄存器的一种抽象模拟。
那么为什么使用程序计数器记录当前线程的执行地址呢?我们都知道CPU需要不停的切换各个线程,这时候从其他线程切换到这个线程时,根据程序计数器就知道该从哪里开始执行了。JVM 的字节码解释器就需要通过改变程序计数器的值来明确下一条应该执行什么样的字节码指令,由于这个原因,必须为每个线程分配一个程序计数器,这样各个线程就可以互不干扰。 1.2.2 Java 虚拟机栈(Java Virtual Machine Stacks)首先我们需要学会区分栈和堆: 栈是运行时的单位,而堆时存储的单位。
1.2.2.1 Java虚拟机栈概述每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧,对应着一次方法的调用。Java 虚拟机栈是线程私有的。生命周期和线程一致。栈中的数据都以栈帧为单位存储。栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。 作用:主要负责Java程序的运行,保存方法内的局部变量,还有部分结果,还参与方法的调用和返回。 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。JVM 直接对 Java 栈的操作只有两个:
注意:对于栈来说不存在垃圾回收问题。 1.2.2.2栈的运行原理
1.2.2.3 栈帧的内部每个栈帧中都有:局部变量表、操作数栈、动态链接、方法返回地址、一下附加信息。 1.2.2.3.1局部变量表(Local Variables)局部变量表用于存放方法参数和方法内部定义的局部变量。 对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。 1.2.2.3.2 操作数栈(Operand Stack)(或表达式栈)程序中的所有计算过程都是在借助于操作数栈来完成的。 1.2.2.3.3 动态链接(Dynamic Linking) (或指向运行时常量池的方法引用)因为在方法执行的过程中有可能需要用到类中的常量或方法,所以必须要有一个引用指向运行时常量池。 1.2.2.3.4 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。 1.2.2.3.5 一些附加信息例如和调试相关的信息,这部分信息完全取决于不同的虚拟机实现。 1.2.3 本地方法栈
内存溢出方面也是相同的。
1.2.4 堆内存堆内存概述:
我们在之后在细讲堆内存的区域划分及垃圾回收机制 1.2.5方法区方法区,是一个被线程共享的内存区域。其中主要存储加载的类字节码、class/method/field 等元数据、static final 常量、static 变量、编译器编译后的代码等数据。另外,方法区包含了一个特殊的区域“运行时常量池”。 结语这次就先写这些,文中如果存在不对的地方,欢迎各位读者批评指正。我会在今后更新本地方法接口、执行引擎和垃圾回收机制等相关内容,如果大家感兴趣,可以关注博主,我们一起交流学习。
|
|