java常见面试考点
往期文章推荐:
java常见面试考点(十六):类加载器的常见考点
深入浅出JVM系列(二):垃圾收集算法
深入浅出JVM系列(三):JVM生命周期
【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权);
本博客的内容来自于:深入浅出JVM系列(一):JVM内存结构;
学习、合作与交流联系q384660495;
本博客的内容仅供学习与参考,并非营利;
一、前言
这篇文章是JVM这个专题的第一篇文章,对于这一个专题,我打算尽可能的深入浅出的记录下JVM里必须要掌握的所有知识点,希望和大家一起进步。
二、JVM的整体结构
首先,先看一下JVM的整体结构,如下图所示:
JVM的内存结构可以分为上中下3层。
上层主要是类加载子系统,负责将字节码文件加载到内存中生成class对象。类加载又分为具体的三个环节,加载(loading)、链接(linking)、初始化(Initialization)。详情可以参考我的这一篇文章java常见面试考点(十六):类加载器的常见考点
中层是运行时数据区,包括方法区、堆区、栈区(通常所说的java虚拟机栈)、程序计数器、本地方法栈。
下层是执行引擎,Java本地接口 (JNI),本地方法库。JVM将class文件加载到内存中以后,执行引擎负责执行代码。执行引擎会将高级语言翻译成机器指令。Java本地接口 (JNI): JNI 会与本地方法库进行交互并提供执行引擎所需的本地库。本地方法库:它是一个执行引擎所需的本地库的集合。执行引擎包括解释器、JIT编译器、垃圾回收器3部分。
解释器能快速的解释字节码,但执行却很慢。 解释器的缺点就是,当一个方法被调用多次,每次都需要重新解释。
JIT编译器消除了解释器的缺点。执行引擎利用解释器转换字节码,但如果是重复的代码则使用JIT编译器将全部字节码编译成本机代码。本机代码将直接用于重复的方法调用,这提高了系统的性能。
- 中间代码生成器 – 生成中间代码
- 代码优化器 – 负责优化上面生成的中间代码
- 目标代码生成器 – 负责生成机器代码或本机代码
- 探测器(Profiler) – 一个特殊的组件,负责寻找被多次调用的方法
垃圾回收器:收集并删除未引用的对象。可以通过调用"System.gc()"来触发垃圾回收,但并不保证会确实进行垃圾回收。JVM的垃圾回收只收集哪些由new关键字创建的对象。所以,如果不是用new创建的对象,你可以使用finalize函数来执行清理。详情可参考我的这一篇文章深入浅出JVM系列(二):垃圾收集算法
三、运行时数据区
JVM 的运行时数据区主要包括:堆、栈、方法区、程序计数器等。而 JVM 的优化问题主要在线程共享的数据区中:堆、方法区。
1、程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,可以看作是当前线程所执行字节码的行号指示器,指向下一个将要执行的指令代码,由执行引擎来读取下一条指令。更确切的说,一个线程的执行,是通过字节码解释器改变当前线程的计数器的值,来获取下一条需要执行的字节码指令,从而确保线程的正确执行。
为了确保线程切换后(上下文切换)能恢复到正确的执行位置,每个线程都有一个独立的程序计数器,各个线程的计数器互不影响,独立存储。也就是说程序计数器是线程私有的内存。
如果线程执行 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是 Native 方法,计数器值为Undefined。
此内存区域是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域。
2、虚拟机栈和本地方法栈
JVM 中的栈包括 Java 虚拟机栈和本地方法栈,两者的区别就是,Java 虚拟机栈为 JVM 执行 Java 方法服务,本地方法栈则为 JVM 使用到的 Native 方法服务。两者作用是极其相似的,本文主要介绍 Java 虚拟机栈,以下简称栈。
JDK 中有很多方法是使用 Native 修饰的。Native 方法不是以 Java 语言实现的,而是以本地语言实现的(比如 C 或 C++)。比如通知垃圾收集器进行垃圾回收的代码 System.gc(),就是使用 native 修饰的。
栈是后进先出的结构。他是线程私有的,他的生命周期与线程相同。每个线程都会分配一个栈的空间,即每个线程拥有独立的栈空间。
栈帧是栈的元素。每个方法在执行时都会创建一个栈帧。栈帧中存储了局部变量表、操作数栈、动态连接和方法出口等信息。每个方法从调用到运行结束的过程,就对应着一个栈帧在栈中压栈到出栈的过程。
局部变量表
栈帧中,由一个局部变量表存储数据。局部变量表中存储了基本数据类型(boolean、byte、char、short、int、float、long、double)的局部变量(包括参数)、和对象的引用(String、数组、对象等),但是不存储对象的内容。局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。
局部变量的容量以变量槽(Variable Slot)为最小单位,每个变量槽最大存储32位的数据类型。对于64位的数据类型(long、double),JVM 会为其分配两个连续的变量槽来存储。以下简称 Slot 。
JVM 通过索引定位的方式使用局部变量表,索引的范围从0开始至局部变量表中最大的 Slot 数量。普通方法与 static 方法在第 0 个槽位的存储有所不同。非 static 方法的第 0 个槽位存储方法所属对象实例的引用。
为了尽可能的节省栈帧空间,局部变量表中的 Slot 是可以复用的。方法中定义的局部变量,其作用域不一定会覆盖整个方法。当方法运行时,如果已经超出了某个变量的作用域,即变量失效了,那这个变量对应的 Slot 就可以交给其他变量使用,也就是所谓的 Slot 复用。Slot 复用虽然节省了栈帧空间,但是会伴随一些额外的副作用。比如,Slot 的复用会直接影响到系统的垃圾收集行为。详情参考一文搞懂JVM内存结构
操作数栈
操作数栈是一个后进先出栈。操作数栈的元素可以是任意的Java数据类型。方法刚开始执行时,操作数栈是空的,在方法执行过程中,通过字节码指令对操作数栈进行压栈和出栈的操作。通常进行算数运算的时候是通过操作数栈来进行的,又或者是在调用其他方法的时候通过操作数栈进行参数传递。操作数栈可以理解为栈帧中用于计算的临时数据存储区。详情参考一文搞懂JVM内存结构
使用 -Xss 设置栈大小,通常几百K就够用了。由于栈是线程私有的,线程数越多,占用栈空间越大。
动态链接
Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态链接(Dynamic Linking)。
方法返回
无论方法是否正常完成,都需要返回到方法被调用的位置,程序才能继续进行
3、方法区
方法区同 Java 堆一样是被所有线程共享的区间,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。更具体的说,静态变量+常量+类信息(版本、方法、字段等)+运行时常量池存在方法区中。常量池是方法区的一部分。
注:JDK1.8 使用元空间 MetaSpace 替代方法区,元空间并不在 JVM中,而是使用本地内存。对于元空间和方法区的改动,可以参考我的这篇文章:java常见面试考点(十七):为什么要去除永久代,换成元空间元空间两个参数:
MetaSpaceSize:初始化元空间大小,控制发生GC阈值 MaxMetaspaceSize :
限制元空间大小上限,防止异常占用过多物理内存
4、堆
堆是Java虚拟机所管理的内存中最大的一块存储区域。堆内存被所有线程共享。主要存放使用new关键字创建的对象。所有对象实例以及数组都要在堆上分配。垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间(收集的是对象占用的空间而不是对象本身)。
Java堆分为年轻代(Young Generation)和老年代(Old Generation);年轻代又分为伊甸园(Eden)和幸存区(Survivor区);幸存区又分为From Survivor空间和 To Survivor空间。
年轻代存储“新生对象”,我们新创建的对象存储在年轻代中。当年轻内存占满后,会触发Minor GC,清理年轻代内存空间。
老年代存储长期存活的对象和大对象。年轻代中存储的对象,经过多次GC后仍然存活的对象会移动到老年代中进行存储。老年代空间占满后,会触发Full GC。
5、非堆
按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。 堆是在 Java 虚拟机启动时创建的。在JVM中,堆之外的内存称为非堆内存(Non-heap memory)”。
可以看出JVM主要管理两种类型的内存:堆和非堆。
简单来说:
堆就是Java代码可及的内存,是留给开发人员使用的;
非堆就是JVM留给自己用的,所以
- 方法区、
- JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、
- 每个类结构(如运行时常数池、字段和方法数据)
- 方法和构造方法 的代码
都在非堆内存中。
堆外内存的优点和缺点
堆外内存,其实就是不受JVM控制的内存。相比于堆内内存有几个优势:
- 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作(可能使用多线程或者时间片的方式,根本感觉不到)
- 加快了复制的速度。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。
而福之祸所依,自然也有不好的一面:
- 堆外内存难以控制,如果内存泄漏,那么很难排查
- 堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合