运行时数据区
运行时数据区又分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器
线程私有的有:程序计数器、虚拟机栈、本地方法栈
线程共享的有:堆、方法区


堆内存
分为 新生代(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation,已废弃), 三者共同构成 JVM 堆的核心存储区域,主要用于存放 Java 对象实例。
- 新生代
- 新生代是 对象刚被创建时分配内存的区域,主要存放 生命周期较短的对象(比如方法局部变量、临时创建的对象)。
- 新生代进一步分为三个部分:
- Eden 区(伊甸园):
对象最初创建时都放在这里,空间较大(占新生代的 80% 左右)。当 Eden 区满时,会触发 Minor GC,存活的对象会被复制到 Survivor 区。 - Survivor 区(幸存者区):
分为 From Survivor 和 To Survivor 两个区域(各占新生代的 10% 左右)。
每次 Minor GC 后,Eden 区和 From Survivor 区中存活的对象会被复制到 To Survivor 区,然后清空 Eden 和 From Survivor。
经过多次 GC 仍然存活的对象(默认 15 次),会被晋升到 老年代。
- Eden 区(伊甸园):
- 老年代
- 老年代用于存放 生命周期较长的对象(比如缓存数据、单例对象)。
这些对象通常是从新生代晋升而来,经过多次 Minor GC 后仍然存活。 - 垃圾回收
老年代的 GC 称为 Major GC(或 Full GC),执行频率较低,但耗时较长。
当老年代空间不足时,会触发 Full GC,回收整个堆内存(包括新生代和老年代)。
Full GC 会导致应用程序停顿(Stop-The-World),因此需要尽量减少其发生频率。
- 永久代(Permanent Generation,已废弃)
- 作用(JDK 1.7 及之前)
永久代用于存放 类的元数据(比如类的结构信息、方法、字段)、常量池、静态变量 等。
它与堆的其他区域不同,不存放对象实例,而是存放类相关的信息。 - 为什么废弃?
永久代的大小难以预测:类的数量可能很多,容易导致内存溢出(OutOfMemoryError: PermGen space)。
JDK 1.8 及之后,永久代被 元空间(Metaspace) 取代,元空间使用 本地内存,不再占用堆内存,从而避免了 PermGen 溢出问题。
程序计数器:
记录当前线程正在执行的字节码指令的地址,物理上程序计数器是使用"寄存器"完成的。
虚拟机栈:
● 也叫线程栈,每个线程运行时所需要的内存,一个栈是由多个栈帧组成
● 栈帧对应着每次方法调用时所占有的内存,存储的内容是:方法参数、方法内局部变量、方法返回地址
● 一个线程每时刻只能有一个活动栈帧,对应当前正在执行的方法
● 垃圾回收不涉及到 栈内存
● 方法递归过多 会导致 java.lang.StackOverflowError 栈内存溢出
本地方法栈
● 本地方法接口运行时所需要的内存空间
● 以 native 修饰的方法就是本地方法,本地方法不是用Java写的(没有方法实现的,只有一个接口供Java调用),而是用C或C++编写,因为 Java 有些时候不能直接和操作系统底层打交道,因此Java通过接口间接调用C或C++编写的本地方法来与操作系统底层的API打交道。即Java通过本地方法调用操作系统底层功能。
方法区
● 线程共享的区域,存储的是类信息、静态变量、常量、编译后的代码、运行时常量池
● JDK7方法区的实现叫:永久代,占用的是堆的内存空间,大小固定
● JDK8方法区的实现叫:元空间,占用的是本地内存的空间,大小自动调整
TLAB
定义:TLAB 是 JVM 的一种内存分配的优化机制。用于提高多线程环境下,内存分配的效率。
问题引入:多线程同时new对象时,JVM为其分配内存,为了防止内存重复分配,需要加入互斥锁,降低了内存分配的速度。
解决方案:所以引入了TLAB的概念,Threal Local Allocate Buffer,是每个线程在Eden区专属的一块区域,当线程的TLAB区域未满时,可以直接在其中分配内存,不需要加锁,提高内存分配速度。
当一个线程创建的时候,JVM会为这个线程分配一个固定大小的TLAB空间
TLAB 仅优化内存分配:TLAB 的线程安全仅体现在内存分配过程中,与对象本身的线程安全性无关