目录
运行时数据区
前言
已经找到工作了,分享秋招时的笔记。祝大家都能顺利找到自己心仪的工作。
面试题
JVM 内存区域 / 运行时数据区?
JVM 运行时数据区包括程序计数器、虚拟机栈、本地方法栈、堆、方法区
程序计数器、虚拟机栈、本地方法栈是线程私有的,堆和方法区是线程共享的
程序计数器:
- 程序计数器是当前线程所执行的字节码的行号指示器
- 是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError/OOM 情况的区域
虚拟机栈:
- 描述 Java 方法执行的线程内存模型
- 每个方法被执行时会创建一个栈帧,用于存储局部变量表、操作数栈等信息
- 虚拟机栈是线程私有的,生命周期与线程相同
- 异常情况:如果栈深度超过虚拟机允许的深度,会抛出 StackOverflowError 异常;如果栈容量扩展失败,会抛出 OutOfMemoryError 异常
本地方法栈:
- 与虚拟机栈类似,但为虚拟机使用的本地 Native 方法服务
堆:
- 堆是线程共享的,存放对象实例,几乎所有对象实例都在堆上分配
- 通过参数 -Xmx 和 -Xms 设置堆的大小
- 在 Java 堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)以提升对象分配效率
- 细分为新生代(Eden 区、Survivor 区 S0 和 S1)和老年代
- 如果 Java 堆没有足够内存进行实例分配且无法再扩展,会抛出 OutOfMemoryError 异常
方法区:
- 用于存储已加载的类型信息、常量、静态变量等数据
- 和 Java 堆一样,是线程共享的
- 在 JDK8 之前,方法区的实现为永久代,JDK8 之后为元空间
说一下 JDK1.6、1.7、1.8 内存区域的变化?
在 JDK 1.6、1.7 和 1.8 版本中,内存区域的变化主要体现在方法区的实现方式上
- JDK1.6: 方法区的实现是永久代
- JDK1.7: 讲字符串常量池和静态变量从永久代中移到堆
- JDK1.8 去除永久代的概念,使用元空间;在直接内存划分区域作为元空间,运行时常量池、类常量池都移动到元空间
为什么使用元空间替代永久代作为方法区的实现?
- 永久代是固定大小的,无法动态调整;元空间使用本地内存作为存储区域,可以根据系统的物理内存动态调整大小
Java 堆的内存分区了解吗?
- 按照垃圾收集,将 Java 堆划分为新生代和老年代
- 新生代存放存活时间短的对象,每次回收后存活的少量对象,逐步升到老年代
- 新生代分为三个区域:Eden、S1、S2,比例是 8:1:1
运行时常量池?
- 运行时常量池是方法区的一部分
- 常量池表:用于存放编译器生成的字面量和符号引用,在类加载后存放到方法区的运行时常量池中
- 具有动态性,不要求常量一定只有编译期才能产生
- 常量池在无法申请到内存时会抛出 OutOfMemoryError 错误
字符串常量池了解吗?
- 字符串常量区是 JVM 为了提高性能和较少内存消耗针对字符串类专门开辟的一块区域
- 为了避免字符串的重复创建
// 在堆中创建字符串对象”ab“
// 将字符串对象”ab“的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true
为什么将字符串常量池移动到堆中?
- 因为方法区的 GC 回收效率太低,只有在整堆收集 (Full GC) 时才会执行 GC
- Java 程序中通常有大量的字符串等待回收,将字符串常量池放在堆里,能够更加高效的回收字符串内存