文章目录
- 一、JVM简介
- 1.1 JVM概念
- 1.2 什么是Java虚拟机呢?Java虚拟机的好处是什么呢?
- 二、JVM整体组成部分
- 三、类加载器
- 3.1 类加载子系统
- 3.2 类加载过程
- 3.2.1 装载(Load)
- 3.2.2 链接(Link)
- 3.2.3 初始化(Initialize)
- 四、运行时数据区
- 4.1 方法区(Method Area)
- 4.2 Java堆(Java Heap)
- 4.3 程序计数器(Program Counter Register)
- 4.4 Java虚拟机栈(Java Virtual Machine Stacks)
- 4.5 本地方法栈(Native Method Stack)
- 五、 执行引擎
- 5.1 概述
- 5.2 什么是解释器?什么是 JIT 编译器?
- 六、 本地方法接口
- 6.1 什么是本地方法
- 6.2 为什么要使用 Native Method
- 七、 垃圾回收
- 7.1 垃圾回收概述
- 7.2 新生代、老年代、永久代(方法区)的区别
- 7.3 JVM的垃圾回收机制
- 7.4 Minor GC、Major GC、Full GC区别及触发条件
- 7.5 垃圾收集器
- 7.5.1 什么是垃圾收集器?
- 7.5.2 垃圾回收器详解
一、JVM简介
1.1 JVM概念
JVM是Java Virtual Machine(Java虚拟机)的缩写。
1.2 什么是Java虚拟机呢?Java虚拟机的好处是什么呢?
虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
简单来说JVM是用来解析和运行Java程序的。
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够一次编译,到处运行的原因。
二、JVM整体组成部分
- 类加载器(ClassLoader)
- 运行时数据区(Runtime Data Area)
- 执行引擎(Execution Engine)
- 本地库接口(Native Interface)
三、类加载器
3.1 类加载子系统
类加载子系统负责从文件系统或者网络中加载class文件。classLoader只负责class文件的加载,至于它是否可以裕兴,则由Execution Engine决定。
加载的类信息存放于一块称为方法区的内存空间。
class file 存在于硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载 JVM 当中来,根据这个模板实例化出 n 个实例。
class file 加载到 JVM 中,被称为 DNA 元数据模板. 此过程就要有一个运输工具(类加载器 Class Loader),扮演一个快递员的角色。
3.2 类加载过程
3.2.1 装载(Load)
- 通过类名(地址)获取此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转换为方法区(元空间)的运行时结构。
- 在内存中生成一个代表这个类的java.lang.class对象,作为这个类的各种数据的访问入口。
3.2.2 链接(Link)
- 验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致;验证文件格式是否一致;class 文件在文件开头有特定的文件标识(字节码文件都以 CA FE BA BE 标识开头);元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合java 语言规范的要求。
- 准备:准备阶段则负责为类的静态属性分配内存,并设置默认初始值;不包含用 final 修饰的 static 常量,在编译时进行初始化。
- 解析:将类的二进制数据中的符号引用替换成直接引用(符号引用是 Class 文件的逻辑符号,直接引用指向的方法区中某一个地址)
3.2.3 初始化(Initialize)
对类的静态变量,静态代码块执行初始化操作 执行了Clinit方法
四、运行时数据区
4.1 方法区(Method Area)
方法区是所有线程共享的内存区域,它用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
4.2 Java堆(Java Heap)
- java堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
- 在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”。
4.3 程序计数器(Program Counter Register)
- 程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号)
- 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
4.4 Java虚拟机栈(Java Virtual Machine Stacks)
- java虚拟机是线程私有的,它的生命周期和线程相同。
- 虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
4.5 本地方法栈(Native Method Stack)
- 本地方法栈很好理解,他很栈很像,只不过方法上带了 native 关键字的栈字
- 它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务
- native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。
- 同理可得,本地方法栈中就是C和C++的代码
五、 执行引擎
5.1 概述
- 执行引擎是 Java 虚拟机核心的组成部分之一。
- JVM 的主要任务是负责装载字节码到其内部,但字节码并不能够直接运行在操作系统之上,因为字节码指令并非等价于本地机器指令,它内部包含的仅仅只是一些能够被 JVM 所识别的字节码指令、符号表,以及其他辅助信息。
那么,如果想要让一个 Java 程序运行起来,执行引擎(Execution Engine)的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。简单来说,JVM 中的执行引擎充当了将高级语言翻译为机器语言的译者。
5.2 什么是解释器?什么是 JIT 编译器?
- 解释器: 当 Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行。
- JIT(Just In Time Compiler)编译器:
就是虚拟机将源代码一次性直接编译成和本地机器平台相关的机器语言,但并不是马上执行。
六、 本地方法接口
6.1 什么是本地方法
- 简单来讲, 一个 Native Method 就是一个 java 调用非 java 代码的接口 ,一个Native Method是这样一个 java 方法:该方法的底层实现由非 Java 语言实现,比如 C。这个特征并非 java特有,很多其他的编程语言都有这一机制在定义一个 native method 时,并不提供实现体(有些像定义一个 Java interface),因为其实现体是由非 java 语言在外面实现的。
- 关键字 native 可以与其他所有的 java 标识符连用,但是 abstract 除外。
6.2 为什么要使用 Native Method
Java 使用起来非常方便,然而有些层次的任务用 java 实现起来不容易,或者我们对程序的效率很在意时,问题就来了。
- 与 java 环境外交互。
有时 java 应用需要与 java 外面的环境交互,这是本地方法存在的主要原因。 你可以想想 java 需要与一些底层系统,如某些硬件交换信息时的情况。本地方法正式这样的一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解java 应用之外的繁琐细节。 - Sun 的解释器是用 C 实现的,这使得它能像一些普通的 C 一样与外部交互。
jre大部分是用 java 实现的,它也通过一些本地方法与外界交互。例如:类 java.lang.Thread 的 setPriority()方法是用 Java 实现的,但是它实现调用的是该类里的本地方法 setPriority0()。
七、 垃圾回收
7.1 垃圾回收概述
- Java 和 C++语言的区别,就在于垃圾收集技术和内存动态分配上,C++语言没有垃圾收集技术,需要程序员手动的收集。
- 关于垃圾收集有三个经典问题:
哪些内存需要回收?
什么时候回收?
如何回收?
7.2 新生代、老年代、永久代(方法区)的区别
- Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
- 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。
7.3 JVM的垃圾回收机制
垃圾回收机制简称GC,GC主要用于Java堆的管理。Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。
7.4 Minor GC、Major GC、Full GC区别及触发条件
Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。
Major GC是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。
Full GC是清理整个堆空间,包括年轻代和老年代
Minor GC 触发条件一般为:
- eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。
- 新创建的对象大小 > Eden所剩空间
Major GC和Full GC 触发条件一般为:
Major GC通常是跟full GC是等价的
- 每次晋升到老年代的对象平均大小>老年代剩余空间
- MinorGC后存活的对象超过了老年代剩余空间
- 永久代空间不足
7.5 垃圾收集器
7.5.1 什么是垃圾收集器?
- 垃圾收集器是垃圾回收算法(引用计数法、标记清楚法、标记整理法、复制算法)的具体实现,不同垃圾收集器、不同版本的JVM所提供的垃圾收集器可能会有很在差别。
- 这里以JDK8为准:
图中展示了7种不同分代的收集器:
Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old、G1
而它们所处区域,则表明其是属于新生代还是老年代的收集器:
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS、Serial Old、Parallel Old
- 整堆收集器:G1
7.5.2 垃圾回收器详解
- Serial 收集器:新生代。发展历史最悠久的收集器。它是一个单线程收集器,它只会使用一个 CPU或者线程去完成垃圾收集工作,而且在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
- ParNew 收集器:新生代。Serial 的多线程版本,即同时启动多个线程去进行垃圾收集。
- Parallel Scavenge 收集器:新生代。和 ParNew 的关注点不一样,该收集器更关注吞吐量,尽快地完成计算任务。
- Serial Old 收集器:Serial 的老年代版本,使用标记 - 整理算法。
- Parallnel old 收集器,多线程:Parallel 的老年代版本,使用标记 - 整理算法。
- CMS 收集器:老年代。是一种以获取最短回收停顿时间为目标的收集器,适用于互联网站或者 B/S 系统的服务端上。
- G1收集器:分代收集器。当今收集器技术发展最前沿成果之一,是一款面向服务端应用的垃圾收集器。G1可以说是CMS的终极改进版,解决了CMS内存碎片、更多的内存空间登问题。虽然流程与CMS比较相似,但底层的原理已是完全不同。