Java内存模型的核心目标是解决多线程并发访问共享变量时可能出现的数据不一致性问题。 在多线程环境下,如果不加控制地访问共享变量,可能会导致数据竞争、内存可见性问题以及指令重排序等并发问题。而Java内存模型通过定义一系列规则和原则,来确保多线程程序能够正确地执行,保证共享变量的可靠性和一致性。
- 原子性: 原子性是指一个操作是不可中断的,即使在多线程环境下,一个操作执行过程中不会被其他线程干扰,要么执行完毕,要么不执行,不存在执行一部分的情况。在Java中,我们可以通过synchronized**关键字或者使用原子类来实现原子操作,从而保证多线程环境下的数据一致性。
- 可见性: 可见性是指当一个线程修改了共享变量的值,其他线程能够立即看到修改后的值。在多线程环境下,由于每个线程都有自己的工作内存,如果没有同步机制**的保护,可能会导致修改的值对其他线程不可见,从而引发错误的结果。因此,为了保证可见性,我们可以使用volatile关键字来修饰共享变量,或者使用synchronized关键字来确保线程间的数据同步。
- 有序性: 有序性是指程序执行的顺序按照代码的先后顺序执行,但在多线程环境下,由于指令重排序的存在,可能会导致程序执行顺序与代码顺序不一致的情况。为了解决这个问题,Java内存模型采用happens-before规则来确定指令重排序的行为,从而保证程序的执行顺序符合预期。
Java内存模型(JMM)
Java内存模型(JMM)是Java虚拟机(JVM)用于管理内存的框架,它定义了应用程序中变量的访问规则以及在多线程环境中变量如何和何时更新。
内存区域划分
- 堆(Heap) : 所有的Java对象都在这里分配内存。它是垃圾回收器的主要工作区域。根据对象的存活周期,堆可以细分为新生代、老年代和永久代(或元空间)。(gc主要针对堆)
- 栈(Stack) : 每个线程都有自己的栈,用于存储局部变量和部分方法信息。栈内存由系统自动分配和释放。
- 方法区(Method Area) : 存储每个类的结构信息,包括运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容。
- 程序计数器(PC Register) : 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
- 本地方法栈(Native Method Stack) : 专为处理本地方法执行而设计。
堆内存
堆是Java虚拟机中最大的一块内存区域,用于存储Java应用程序创建的所有对象和数组。不同于栈内存,堆内存并不随着方法调用的结束而销毁,而是由垃圾回收器管理和释放。
java
public class MyClass {
public static void main(String[] args) {
int[] numbers = new int[10]; // 在堆上分配数组内存
// ...
}
}
堆内存可分为以下几个部分:
- 新生代(Young Generation) : 新创建的对象首先被分配在这里。新生代中的对象生命周期短,这里的垃圾回收频繁进行。
- 老年代(Old Generation) : 长期存活的对象会被转移到这里。老年代的大小一般远大于新生代,其垃圾回收频率较低。
- 永久代/元空间(PermGen/Metaspace) : 存储类的元数据,Java 8中被元空间所取代。
栈内存
每当调用新的方法时,都会在栈中创建一个新的栈帧,用于存储局部变量和部分方法信息。当方法结束时,相应的栈帧就会被销毁。
java
public void greet() {
int a = 10; // 局部变量存储在栈内存中
// ...
}
方法区,程序计数器,本地方法栈
方法区 :用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,该区域也是线程共享的 程序计数器 :主要作用记录线程执行到了哪里。 本地方法栈:Native 方法执行的线程内存模型。
这几个不是特别重要,先这样一笔带过。
垃圾回收(GC)
垃圾回收是JVM自动管理内存的过程。其目标是识别和释放不再被使用的对象所占用的内存。
GC算法
- 标记-清除(Mark-Sweep) : 标记所有从根集合可达的对象,然后清除所有未标记的对象。
- 复制(Copying) : 将内存分为两块,每次只使用其中一块。当这一块内存用完时,将存活的对象复制到另一块内存上。
- 标记-整理(Mark-Compact) : 类似于标记-清除,但在清除后会移动所有存活的对象,以消除碎片。
GC触发条件
- 当堆内存不足时。
- System.gc() 被显式调用时(尽管不建议)。
内存泄漏与优化
内存泄漏指的是程序中已分配的内存空间,在不再需要使用后,未被释放或无法被GC回收。
内存模型的堆和gc,深入研究
Java内存模型(JMM)和Java堆是Java虚拟机(JVM)中管理内存的核心组成部分,理解它们对于编写高效和稳健的Java应用程序至关重要。
Java内存模型(JMM)
Java内存模型主要关注多线程编程中的内存访问和同步问题。它定义了如何在并发编程环境中处理可见性、原子性、有序性等关键问题。
关键特性
- 可见性(Visibility) :确保一个线程对共享变量的修改对其他线程可见。JMM通过
volatile
关键字、锁(synchronized块)和final字段来实现。 - 原子性(Atomicity) :保证某些操作是不可中断的,要么完全执行,要么完全不执行。可以通过
synchronized
块和java.util.concurrent
包中的原子类来实现。 - 有序性(Ordering) :防止编译器和处理器对指令进行重排序,确保程序的执行顺序与代码顺序相符。可以通过
volatile
和锁实现。
目的
JMM的设计目的是提供一种机制,使得并发程序在不同的JVM和硬件架构上都能保持一致的行为。
Java堆
Java堆是JVM中内存管理的主要区域,用于存储对象实例和数组。
特点
- 共享区域:堆内存在所有线程间共享。
- 垃圾回收:堆内存的管理涉及到垃圾回收(GC),用于自动回收不再使用的对象,从而防止内存泄漏。
- 大小调整:堆的大小可以动态调整,通过JVM启动参数设置初始大小和最大大小。
- 结构:在现代JVM中,堆通常分为几个区域:新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation,被Metaspace取代)。
管理
- 新生代:大多数新创建的对象首先被分配在新生代。新生代又分为一个Eden区和两个Survivor区。对象首先在Eden区分配,随后通过垃圾回收的次数移动到Survivor区或老年代。
- 老年代:存储长期存活的对象。当新生代中的对象经过一定数量的垃圾回收后仍然存活,它们会被移动到老年代。
- Metaspace:用于存储类的元数据,JDK 8中取代了永久代(PermGen)。
垃圾回收(GC)
垃圾回收是自动管理Java应用程序内存的过程。它监控创建的对象,自动识别和回收不再被使用的对象。
垃圾回收器
Java提供了多种垃圾回收器,每种都有不同的特点和用途:
- Serial GC:单线程收集器,简单但在进行垃圾回收时会暂停其他所有工作线程。
- Parallel GC:多线程版本的Serial GC,用于提高性能。
- Concurrent Mark-Sweep (CMS) GC:并发收集器,目标是减少应用暂停时间。
- G1 GC:旨在更大堆上提供高吞吐量且低暂停时间的垃圾收集。
垃圾回收过程
- 标记:识别那些仍然被应用程序引用的对象。
- 清除:回收那些不再被引用的对象所占用的内存空间。
- 压缩(某些GC算法):压缩剩余对象,减少内存碎片,为新对象分配空间。