概念
- JMM,Java Memory Model,定义JVM在计算机内存(RAM)中的工作方式。
- JVM是整个计算机的虚拟模型,所以JMM隶属于JVM。
抽象模型
模型
- 线程之间的共享变量储存在主内存中。
- 每个线程都有一个私有的本地内存,储存了该线程读写共享变量的副本。
- 本地内存是一个抽象概念,涵盖缓存、写缓冲区、寄存器、其他硬件和编译器优化
通信的实现
- 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
- 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
JVM内存
- 分为线程共享和线程隔离的区域。
程序计数器
- 可看做当前线程所执行的字节码的行号指示器。
- 线程执行java方法时:虚拟机字节码指令的地址。
- 线程执行native方法时:空(undefined)。
- 唯一没有规定任何OutOfMemoryError的区域。
虚拟机栈
- 描述java方法执行的内存模型:每个方法执行时创建栈帧,存储局部变量表、操作数栈、动态链接、方法出口。
- 局部变量表存储基本数据类型、引用类型,其中64位的long和double占用2个局部变量空间。
- 异常:
- StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
- OutOfMemoryError:虚拟机栈扩展时无法申请到足够的内存。
本地方法栈
- 类似java虚拟机栈,但是使用native方法服务。
- Sun的HotSpot 虚拟机将本地方法栈和虚拟机栈合并。
堆
- 存储对象实例和数组(JIT编译器的优化使得对象并不一定存储在堆上)。
- OutOfMemoryError:堆中没有内存完成实例的分配,并且堆无法扩展。
方法区
- 存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译的代码等。
- JIT:将热点代码编译成平台相关的机器码。
- 内存回收的目标:常量池的回收、类型的卸载。
- OutOfMemoryError:方法区无法满足内存分配需求。
运行时常量池
- 方法区的一部分,存放编译期生成的的字面量和符号引用,或者运行期间产生的常量(String.intern()方法)。
直接内存
- 不是虚拟机运行时数据区的一部分,可以通过NIO分配和操作这一堆外内存。
- OutOfMemoryError:各个内存区域的总和大于物理内存的限制。
JAVA对象
- 基于HotSpot虚拟机讨论,普通java对象,不包括数组、Class对象。
创建
- 类加载检查。检查常量池中是否有对应的类的符号引用,并且这个类是否已被加载、解析、初始化。如没有,执行类加载。
- 分配内存。内存规整时使用指针碰撞,内存不规整时使用空闲列表,两个以上的对象同时分配内存时,需要注意线程安全,解决方法:
- 同步内存分配动作:CAS和失败重试,保证更新的原子性。
- 每个线程预先分配本地线程分配缓冲(Thread Local Allocation Buffer, TLAB),在各自的TLAB上分配内存,只有分配新的TLAB时,才需要同步锁定。
- 初始化零值。除对象头之外的内存空间初始化,保证对象的实例字段不附初始值就可以使用。也可以在TLAB分配时进行。
- 设置对象头。包括属于的类、如何找到类的元数据、对象哈希码、对象GC分代年龄、偏向锁等。
- 执行init方法。按照程序员意愿初始化。
布局
- 对象头。
- 分类
- MarkWord,对象自身运行时数据。哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
- Class指针。指向类元数据,数组还需要包含数组长度。
- 大小
- 在32位系统下,存放Class指针的空间大小是4字节,MarkWord是4字节,对象头为8字节。
- 在64位系统下,存放Class指针的空间大小是8字节,MarkWord是8字节,对象头为16字节。
- 在64位开启指针压缩的情况下 -XX:+UseCompressedOops,存放Class指针的空间大小是4字节,MarkWord是8字节,对象头为12字节。
- 如果对象是数组,那么额外增加4个字节
- 分类
- 实例数据。定义的各种类型字段,存储顺序受虚拟机分配策略和源码中的定义顺序影响,hotspot中相同宽度的字段分配到一起,父类中的变量在子类之前。
- 对齐填充。HotSpot内存管理系统要求对象的大小必须是8字节的整数倍。
访问
- 句柄访问。划分句柄池和实例池,reference存储对象句柄地址,句柄包含对象实例数据和类型数据的地址。
- 直接指针访问。reference存储对象地址,包含指向类型数据的指针。
- 句柄访问的优势:对象移动时只会改变句柄中的实例数据指针,reference本身不变。
- 直接指针访问优势:节省一次指针定位开销,速度更快。HotSpot使用直接指针访问。
OutOfMemoryError异常
- 堆溢出:不停创建对象,并维持 GC Roots 到对象之间的引用避免对象被回收。
- 栈溢出:
- 递归过深。
- 创建过多线程。这是由于栈的空间被每个线程瓜分。
- 方法区和运行时常量池溢出:
- String.intern()方法,在常量池中记录首次出现的实例的引用。
- cglib产生大量的动态类。
- 本机直接内存溢出:Unsafe实例分配过多内存。
- 溢出实例。
内存相关参数
- Xms设置进程堆内存的最小值。
- Xmx设置进程堆内存的最大值。一般来说,为了避免频繁的堆内存震荡,导致系统性能下降,Xms,Xmx设为相等。
- Xss设置每个线程可使用的内存大小。
- Xmn用来设置堆内新生代的大小。老生代的大小:-Xmx减去-Xmn。
- 参考JVM优化总结。
硬件内存模型
- CPU寄存器、CPU缓存、主存。
- JMM和硬件的关系:交叉关系。