文章目录
- 一、简介
- 二、JVM内存区域
- 2.1 方法区
- 2.3.2 永久代和元空间
- 2.2 堆
- 2.1.2 对象的创建和销毁
- 2.2 栈内存
- 2.2.1 栈帧的组成和作用
- 2.2.2 栈的特点
- 2.4 程序计数器
- 2.4.1 程序计数器的作用和使用场景
一、简介
Java 内存模型(Java Memory Model,JMM)是一种规范,定义了 Java 程序中多线程并发访问共享变量时的行为和规则。
二、JVM内存区域
线程共享:方法区、堆
线程独有:栈、程序计数器
2.1 方法区
方法区是JVM中的一块内存区域,在JVM启动时被创建,与堆内存分开管理。方法区的大小可以通过-XX:MetaspaceSize
和-XX:MaxMetaspaceSize
参数进行调整。
存储的内容有:
- 类的结构信息:包括类的字段、方法、父类、接口等
- 常量池
- 静态变量
- 即时编译器编译后的代码
2.3.2 永久代和元空间
在Java虚拟机(JVM)的不同版本中,永久代(Permanent Generation)和元空间(Metaspace)是用于存储类相关信息的内存区域,都是实现方法区的方式。
-
永久代(Permanent Generation):
1.8版本以前有永久代,永久代放在JVM内存中,在某些情况下存在一些问题。例如,如果加载的类过多或过大,永久代的大小可能会不够,导致OutOfMemoryError。由于这些问题,从JDK 8开始,永久代被元空间所取代。 -
元空间(Metaspace):
元空间是JDK 8及更高版本中取代永久代的新的内存区域。元空间同样用于存储类的结构信息、常量池、静态变量和即时编译器编译后的代码等。与永久代不同,元空间的大小不再受限于固定的内存大小,而是根据应用程序的需求进行动态分配。元空间的数据存储在本地内存(Native Memory)中,而不是像永久代那样存储在Java运行时内存中。这意味着元空间的大小受限于可用的物理内存。如果元空间的内存耗尽,JVM依然会抛出OutOfMemoryError。
2.2 堆
堆内存是Java程序中最大的一块内存区域,用于存储对象实例和数组。堆内存可以划分为不同的代,包括新生代(Eden区、Survivor区)和老年代。新创建的对象会被分配到新生代的Eden区,经过垃圾回收后,仍然存活的对象会被移到Survivor区,最终进入老年代。
1.8版本的堆结构:
新生代与老年代的比例是1:2
Eden、s0、s1的比例是8比1比1
1.9及以后的堆结构
G1将内存划分成了多个大小相等的Region(默认是512K),Region逻辑上连续,物理内存地址不连续。同时每个Region被标记成E、S、O、H,分别表示Eden、Survivor、Old、Humongous。其中E、S属于年轻代,O与H属于老年代。
H表示Humongous。从字面上就可以理解表示大的对象(下面简称H对象)。当分配的对象大于等于Region大小的一半的时候就会被认为是巨型对象。H对象默认分配在老年代,可以防止GC的时候大对象的内存拷贝。
2.1.2 对象的创建和销毁
在Java中,对象的创建通过new
关键字实现。当调用new
关键字创建对象时,JVM会在堆内存中分配一块内存空间用于存储对象的实例变量,并执行构造方法对对象进行初始化。对象的销毁由垃圾回收器负责,当对象不再被引用时,垃圾回收器会回收该对象的内存空间。
创建流程:
- ⾸先检查这个指令的参数是否能在常量池中定位到⼀个类的符号引⽤
- 检查这个符号引⽤代表的类是否已被加载、解析和初始化过。如果没有,就先执⾏相应的类加载过程
- 类加载检查通过后,接下来虚拟机将为新⽣对象分配内存。
- 内存分配完成之后,虚拟机将分配到的内存空间(但不包括对象头)都初始化为零值。
- 接下来设置对象头,请求头⾥包含了对象是哪个类的实例、如何才能找到类的元数据信息、对象的 哈希码、对象的 GC 分代年龄等信息
2.2 栈内存
栈分为本地方法栈和java方法栈。
每个线程在创建时都会创建一个java方法栈,栈内会保存一个个的栈帧,每个栈帧对应一个方法。
栈采用先进后出的数据结构,方法的调用和返回都是通过栈帧的入栈和出栈来实现的。
2.2.1 栈帧的组成和作用
栈帧是栈内存中的一个元素,用于存储方法的局部变量表、操作数栈、动态链接、方法返回地址等信息。局部变量表用于存储方法的局部变量和参数,操作数栈用于存储方法执行过程中的操作数和中间结果。栈帧的作用是提供方法调用和执行的环境,保证方法的独立性和数据的隔离性。
2.2.2 栈的特点
- 栈是线程私有的
- 一个方法开始执行栈帧入栈、方法执行完对应的栈帧就出栈,所以虚拟机栈不需要进行垃圾回收
- 虚拟机栈存在OutOfMemoryError、以及StackOverflowError
- 线程太多,就可能会出现OutOfMemoryError,线程创建时没有足够的内存去创建虚拟机栈了
- 方法调用层次太多,就可能会出现StackOverflowError(栈的大小有限)
- 可以通过-Xss来设置虚拟机栈的大小
2.4 程序计数器
2.4.1 程序计数器的作用和使用场景
程序计数器是一块较小的内存区域,用于存储当前线程执行的字节码指令的地址。程序计数器在多线程环境下,每个线程都有独立的程序计数器,用于记录各个线程执行的位置,保证线程切换后能正确恢复执行。