Java内存模型(Java Memory Model),简称JMM,定义了JVM(Java虚拟机)在计算机内存(RAM)中的工作方式,目的是定义一个一致的、跨平台的内存模型,是隶属于JVM的。
内存模型的组成
线程私有
Thread Local
私有数据区域其它线程不可见,生命周期随着线程的的启动而创建,线程结束而销毁。
程序计数器
-
描述
- 线程执行的字节码行号指示器,通过改变这个计数器来获取下一条执行的指令
分支、循环、跳转、异常处理和线程恢复等基础功能都需要依赖这个计数器来完成
- 线程执行的字节码行号指示器,通过改变这个计数器来获取下一条执行的指令
-
特点
- 指向虚拟机字节码指令的位置
唯一一个不会OOM的区域
- 指向虚拟机字节码指令的位置
虚拟机栈
VM Stack
-
虚拟机栈生命周期==线程生命周期
所以对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就over -
一个线程中,每调用一个方法创建一个栈帧(Stack Frame)
-
栈帧结构
- 局部变量表
Local Variable Table - 操作栈
Operand Stack - 动态连接
Dynamic Linking - 返回地址
Return Address - … …
- 局部变量表
本地方法栈
Local Method Stack
- 为本地Native方法服务
线程共享
Thread Shard
Java堆(类实例区)
Objects
-
存放对象实例,也是垃圾收集器主要的管理区域
-
区域
-
新生代
- eden
- from survivor
- to survivor
-
老年代
-
方法区(永久代)
Method Area
-
数据
-
存储虚拟机加载的类信息
-
即时编译后的代码
-
运行时常量池 RCP(Runtime Constant Pool)
-
直接常量(基本类型、String)
-
其它类型方法字段的引用
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
-
-
静态变量
-
-
特点
- 垃圾收集器行为很少在此区域发生,主要针对类的卸载、运行时常量池的回收
-
在java1.8中已移至Matespace区,存储在本地对内存中,不受java堆大小限制,也是为了更好的和JRockit整合
常量和静态变量移至堆中,其余保持不变,不会再有垃圾回收器扫描元空间
直接内存
Direct Memory
不受JVM GC管理
并不是JVM的一部分,可以使用native函数直接分配堆外内存,然后使用DirectByteBuffer对这块内存的引用进行操作,避免在Java堆和Native之间来回复制数据,在一些场景中可以显著提高性能
总结(干货):
- 实例和对象的区别:Class a=new Class();此时的a是实例而不是对象,实例在栈中而对象在堆中,操作实例实际上是间接操作对象。多个实例可以指向一个对象。
- 栈中数据与堆中数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中的对象不一定销毁。因为可能其它变量也指向了这个对象,直到所有线程栈中没有变量指向这个对象时,它才销毁,而且不是马上销毁,需要等垃圾回收器扫描时才能销毁。
- 类的成员变量在不同对象中各不相同,都有自己的存储空间(在堆的对象中)。而类的方法是该类共享的,只有一套,存储在方法区中,当有对象使用方法时压入栈,方法不使用时不占用内存。
- 生命周期:对内存属于java应用程序使用,生命周期与jvm一致;栈内存属于线程私有,生命周期与线程相同
- 引用:不论合适何地创建对象,它总是存储在堆内存空间中,并且栈内存包含对它的引用。栈内存空间仅包含方法原始数据类型
局部变量
和堆空间中对象的引用变量
- 二者抛出异常方式:如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常;堆内存不足时将抛出OutOfMemoryError异常
扩展: jdk8中新增的元数据空间
替换了原有的方法区,主要有如下变化:
1. 将方法区中的静态变量、常量池移到了堆中
2. 元空间大小取决于机器的可用内存大小