🔴 线程共享区域 (Thread-Shared Areas) #JVM/线程共享区域
🔴 特点:所有线程共享同一个内存区域,需要考虑线程安全问题,是垃圾回收的主要工作区域
🔴 1. Java堆 (Java Heap) #JVM/Java堆
🔴 定义:Java堆是线程共享的内存区域,用于存储对象实例和数组
🟠 基本特征:
- 🔴 线程共享:所有线程共享同一个堆空间
- 🔴 对象存储:存储所有对象实例和数组
- 🔴 GC主要区域:是垃圾回收的主要工作区域
- 🔴 异常情况:当堆无法扩展时,抛出OutOfMemoryError
🟠 堆结构:
🟢 1. 新生代 (Young Generation)
- 🟢 Eden区:新对象分配的主要区域
- 🟢 Survivor区:分为S0和S1,存储经过一次GC后存活的对象
- 🟢 Minor GC:新生代的垃圾回收
🟢 2. 老年代 (Old Generation)
- 🟢 Tenured区:存储长期存活的对象
- 🟢 Major GC:老年代的垃圾回收
- 🟢 Full GC:整个堆的垃圾回收
🔴 2. 方法区 (Method Area) #JVM/方法区
🔴 定义:方法区是各个线程共享的内存区域,在虚拟机启动时创建
🟠 基本特征:
- 🔴 共享性:方法区是各个线程共享的内存区域,在虚拟机启动时创建
- 🟠 别名Non-Heap:虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来
- 🔴 存储内容:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 🔴 异常情况:当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
🟠 存储的具体内容:
🟢 1. 类型信息 (Type Information)
- 🟢 类的全限定名
- 🟢 直接父类的全限定名
- 🟢 实现的接口列表
- 🟢 类的访问修饰符
- 🟢 类的类型(普通类、接口、枚举、注解等)
🟢 2. 字段信息 (Field Information)
- 🟢 字段名称、类型、修饰符
- 🟢 字段在类中的位置
🟢 3. 方法信息 (Method Information)
- 🟢 方法名称、返回类型、参数列表
- 🟢 方法修饰符、字节码、异常表
🟢 4. 类变量 (Class Variables)
- 🟢 静态变量存储空间
- 🟢 变量初始化状态和值
🟢 5. 指向类加载器的引用
- 🟢 加载该类的ClassLoader实例
- 🟢 类加载器层次关系
🟢 6. 指向Class对象的引用
- 🟢 Class对象的引用
- 🟢 反射支持和类型检查
🟠 与类加载的关系:
此时回看装载阶段的第2步,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
如果这时候把从Class文件到装载的第(1)和(2)步合并起来理解的话,可以画个图
🔴 实现方式:
🔴 JVM运行时数据区是一种规范,真正的实现:
- 🔴 JDK 8:Metaspace(元空间)
- 🟠 JDK6或7:Perm Space(永久代)
🔴 技术优势:
- 🔴 元空间使用本地内存,可动态扩展
- 🔴 避免了永久代的内存限制,提高了类加载的灵活性
- 🔴 减少了OutOfMemoryError的发生
🔴 3. 常量池 (Constant Pool) #JVM/常量池
🔴 常量池分类:
🟡 3.1 静态常量池 (Static Constant Pool)
🟡 定义:静态常量池是相对于运行时常量池来说的,属于描述class文件结构的一部分
🟡 组成:由字面量和符号引用组成,在类被加载后会将静态常量池加载到内存中也就是运行时常量池
🟡 组成部分详解:
-
字面量 (Literals):
- 🟢 文本字符串常量
- 🟢 数值常量(int、long、float、double)
- 🟢 Final修饰的常量
- 🟢 类名常量
-
符号引用 (Symbolic References):
- 🟢 类和接口的全限定名
- 🟢 字段名和描述符
- 🟢 方法名和描述符
- 🟢 接口方法名和描述符
🟠 3.2 运行时常量池 (Runtime Constant Pool)
🟠 定义:当静态常量池被加载到内存后就会变成运行时常量池
🟠 关键特点:
- 🟠 真正的把文件的内容落地到JVM内存
- 🟠 支持动态解析,可以将符号引用转换为直接引用
- 🟠 每个类或接口都有一个运行时常量池
🔴 3.3 字符串常量池 (String Constant Pool)
🔴 设计理念:字符串作为最常用的数据类型,为减小内存的开销,专门为其开辟了一块内存区域(字符串常量池)用以存放
🔴 位置变化:
- JDK1.6及之前版本:字符串常量池位于永久代(相当于现在的方法区)
- JDK1.7之后:字符串常量池位于Heap堆中
🔴 关键特点:
- 🔴 字符串作为最常用的数据类型,专门开辟内存区域存储
- 🔴 位置变化原因:永久代空间有限,容易OOM,堆内存更灵活
- 🔴 String.intern()方法:将字符串添加到池中,存在则返回引用
- 🔴 字符串拼接性能:+号会创建多个StringBuilder对象
🔴 面试重点问题:
🔴 Java堆相关
🔴 高频问题:
- "Java堆的作用是什么?"
- 存储内容:存储所有对象实例和数组
- 线程共享:所有线程共享同一个堆空间
- GC区域:是垃圾回收的主要工作区域
- 异常情况:当堆无法扩展时,抛出OutOfMemoryError
- "Java堆的结构是怎样的?"
- 新生代:Eden区、Survivor区(S0、S1)
- 老年代:Tenured区
- GC类型:Minor GC、Major GC、Full GC
🟠 中频问题:
- "新生代和老年代的区别?"
- 新生代:存储新创建的对象,Minor GC频率高
- 老年代:存储长期存活的对象,Major GC频率低
- 对象晋升:新生代对象经过多次GC后晋升到老年代
- "如何调整Java堆的大小?"
- 初始大小:-Xms参数设置堆初始大小
- 最大大小:-Xmx参数设置堆最大大小
- 示例:-Xms2g -Xmx4g 设置堆初始2GB,最大4GB
🟡 低频问题:
- "Eden区和Survivor区的作用?"
- Eden区:新对象分配的主要区域
- Survivor区:存储经过一次GC后存活的对象
- 对象移动:Eden区 → Survivor区 → 老年代
🔴 方法区相关
🔴 高频问题:
- "方法区和堆的区别是什么?"
- 存储内容:方法区存储类信息、常量池、静态变量、即时编译代码;堆存储对象实例和数组
- 内存管理:方法区是Non-Heap,堆是Heap
- 生命周期:方法区在JVM启动时创建,堆在运行时动态分配
- GC影响:方法区很少GC,堆是GC的主要区域
- "方法区在JDK8中叫什么?为什么改名?"
- 新名称:JDK8中叫Metaspace(元空间)
- 改名原因:永久代空间固定,容易OutOfMemoryError;元空间使用本地内存,可动态扩展
- 技术优势:元空间避免了永久代的内存限制,提高了类加载的灵活性
🟠 中频问题:
- "方法区存储哪些内容?"
- 类型信息:类的全限定名、父类、接口、访问修饰符
- 常量池:字面量、符号引用、运行时常量池
- 字段信息:字段名称、类型、修饰符、位置
- 方法信息:方法名称、返回类型、参数、字节码、异常表
- 类变量:静态变量存储空间和值
- 引用信息:类加载器引用、Class对象引用
- "方法区什么时候会抛出OutOfMemoryError?"
- 内存不足:当方法区无法满足内存分配需求时
- 类加载过多:加载大量类导致方法区空间不足
- 常量池过大:字符串常量池或运行时常量池占用过多内存
- 解决方案:调整方法区大小参数或优化类加载策略
🟡 低频问题:
- "如何调整方法区大小?"
- JDK8之前:-XX:PermSize(初始大小)、-XX:MaxPermSize(最大大小)
- JDK8之后:-XX:MetaspaceSize(初始大小)、-XX:MaxMetaspaceSize(最大大小)
- 默认值:JDK8默认MetaspaceSize为21MB,MaxMetaspaceSize无限制
- 调优建议:根据应用加载的类数量合理设置,避免过小导致频繁GC
🔴 常量池相关
🔴 高频问题:
- "字符串常量池在JDK不同版本中的位置变化?"
- JDK1.6及之前:位于永久代(方法区)
- JDK1.7之后:位于堆内存中
- 原因:永久代空间有限,容易OOM,堆内存更灵活
- "String.intern()方法的作用是什么?"
- 将字符串添加到字符串常量池中
- 如果池中已存在,返回池中的引用
- 如果不存在,添加到池中并返回引用
🟠 中频问题:
- "字符串拼接的性能问题?"
- 使用+号拼接会创建多个StringBuilder对象
- 推荐使用StringBuilder或StringBuffer
- 编译时优化:常量拼接会被优化为单个字符串