Java内存结构和内存模型
1. Java内存结构
1. 1程序计数器(线程私有)
- 当前线程的所执行字节码的行号指示器,
- 字节码解释器工作时通过改变计数器的值选择下一条需要执行的字节码指令。
- java虚拟机的多线程通过轮流切换分配处理器(对于多核处理器来说是一个内核)执行时间来实现,一个时刻,一个处理器只会执行一条线程。
- 为了保证线程切换后能恢复正确的位置,因此每个线程都有一个独立的程序计数器。
- 线程执行 java 方法时计数器记录正在执行的虚拟机字节码指令的地址;
- 线程执行 native 方法时计数器值为空;
1.2. 虚拟机栈(线程私有)
- 生命周期与线程相同
- 描述java方法执行的内存模型:方法执行时创建一个栈帧存储局部变量表,操作数栈,动态链接,方法出口等信息。方法的调用到完成对应着栈帧的入栈到出栈。
- 局部变量表:存放编译器可知的基本类型数据,对象引用和 returnAddress 类型(指向一条字节码指令的地址)
- 局部变量表所需空间在编译期间完成分配,方法运行期间不会改变其大小。
- 64位长度的long和double类型的数据占用2个局部变量空间(Slot),其余数据类型占用一个
当线程请求的深入大于虚拟机允许的深度, 则抛出StackOverFlowError异常;
当虚拟机栈动态扩展时无法申请到足够的内存,则抛出OutOfMemoryError异常
1.3. 本地方法栈(线程私有)
- 为Native 方法服务。
1.4. Java堆
新生代+老年代 (内存回收采用分代收集 )
新生代:Eden + From Survivor + To Survivor ( 8:1:1 )
1.5. 方法区(线程共享)
Java虚拟机规范把方法区描述为堆的一个逻辑部分.
a. 存储虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
b. HotSpot 使用永久代实现方法区,JDK1.7 将永久代的字符串常量池移出。
c. 此区域的垃圾回收主要针对常量池的回收和类型的卸载。
1.6. 运行时常量池(方法区的一部分)
a. 存储编译期间生成的字面量 和 符号引用,这部分内容在类加载后进入方法区的运行时常量池;
b. 具备动态性,运行期间也可将新的常量放入池中;比如String 的 intern()
c. 受方法区内存限制
符号引用:
- 一组用来描述引用的目标的符号;
直接引用:
- 直接指向目标的指针(指向Class 对象,类变量,类方法的直接引用,也可能是指向方法区的指针);
- 相对偏移量(指向实例变量,实例方法的直接引用);
- 一个能定位到目标的句柄。
1.7. 直接内存
NIO引入一种基于通道(Channel)与缓冲区(Buffer)的I/O方式, 它可以直接使用Native函数直接分配堆外内存, 然后通过DirectByteBuffer对象操作这块内存的引用进行操作.
避免在Java堆和native堆中来回复制数据.
直接内存的分配不受Java堆大小的限制.
2. Java内存模型
2.1 Java内存模型的目的
定义程序中各个变量的访问规则。即在虚拟机中将变量存储到内存和从内存中取出变量的底层细节。
此处的变量包括实例字段,静态字段和构成数组对象的元素,但是不包括局部变量和方法参数,因为它们是线程私有,不存在竞争问题。
2.2 内存中的变量
Java内存模型规定所有的变量存储在主内存中。
线程在工作内存中保存了使用到的主内存中变量的副本拷贝,
线程对变量的操作必须在工作内存中进行,不能直接读写主内存中的变量。
不同线程之间无法访问对方工作内存的变量。
2.3 线程,工作内存和主内存的交互关系
线程之间共享变量值的传递均需要通过主内存来完成。
主内存、工作内存与前面讲解Java内存区域中的Java堆、栈、方法区等不是同一个层次划分,两者无任何关系。
即:内存模型和内存结构是两个不同的概念。