Java的内存模型
它定义了 Java 程序中变量的内存可见性、原子性和有序性,确保了多线程环境下的正确性.
核心问题:
- 内存可见性:线程之间无法及时看到其他线程对共享变量的修改
- 指令重排序:为了提高执行速度,编译器和处理器可能会对指令进行重排序优化
- 线程的安全: 线程的原子性
new 出一个东西的过程是什么?
- 类的加载检查。
- 分配内存,分配内存的方式有 “指针碰撞” 和 “空闲列表” 两种
- 初始化零值,JVM 会将分配到的内存空间都初始化为零值
- 设置对象头
- 执行初始的方法
从JMM来解释:
可见性:
JVM 会通过内存屏障等机制来保证对象的初始化操作在主内存中完成,从而让其他线程能够看到对象的最新状态。
初始化:
MM 规定了一些规则来禁止特定类型的指令重排序,以保证对象的初始化顺序符合程序员的预期。
new出来一个对象,会有什么分配内存的方式:
- 指针碰撞:
-
- 假设 Java 堆中的内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。
- 使用场景:空间压缩整理功能的垃圾收集器,如 Serial、ParNew 等
- 空闲列表:
-
- 如果 Java 堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
- 适用于使用不具备空间压缩整理功能的垃圾收集器,如 CMS 这种基于标记 - 清除算法的垃圾收集器
并发环境下分配内存的问题及解决办法:
- 1.使用了cas的机制,不断地进行重试。在内存分配时,使用 CAS 操作来保证对内存分配指针的原子性更新,如果操作失败则重试,直到成功为止。
- 2.本地线程分配缓存(TLAB):
-
- 每个线程在 Java 堆中预先分配一小块私有内存,称为 TLAB。线程在分配对象时,首先在自己的 TLAB 上进行分配,如果 TLAB 空间足够,就直接在 TLAB 上分配内存,这样就避免了多线程之间的竞争。只有当 TLAB 空间不足时,才会采用 CAS 等方式在公共的堆空间上分配内存。
-
TLAB会给每个线程划分一块小小的区域,比如100KB,但是随着线程的运行比如调用栈特别深,new了很多对象,TLAB内存不够了,这时候需要怎么办?
- 尝试重新分配TLAB
- 大对象直接在Eden分配
- 如果Eden也满了,那么触发Minor GC