一、区域分类:
线程私有数据区:栈(虚拟机栈、本地方法栈)、程序计数器。
线程共享数据区:堆、方法区。
说明:sun hotspot虚拟机, 虚拟机栈、本地方法栈这两区域合一。
二、线程私有
1.程序计数器
可以看作是当前线程执行字节码的行号指示器。多线程是通过线程轮流切换并分配处理器执行的时间方式来实现的。因此,程序计数器可以保证切换后恢复到正确的执行位置。每条线程都有自己独立的程序计数器,互相独立,各不影响。
如果执行Native方法,计数器值为空(undefined)。
场景如下:Thread A 执行到代码第5行,切换到Thread B执行, 执行后再次切换到Thread A,接着从第5行执行,能正确的记住这个位置,并执行,是程序计数器的功劳。
2.虚拟机栈:
服务于java方法(字节码) 。
java方法执行的内存模型:每个方法在执行的同时都会创建栈帧,栈帧存储的是:局部变量表,操作数栈,动态链接,方法出口等。栈,先进后出。
局部变量表:存放编译器可以的各种基本数据类型(8大基本类型)、对象引用(reference类型,可理解为句柄)、returnAddress类型(指向一条字节码指令的地址)。这一块也是我们最关注的。
操作数栈:可以理解为栈的工作区,操作局部变量表中的数据。
动态链接:将符号引用解析为直接引用的过程。
方法出口:目前我的理解是,记住方法的出口,相当一个标记。可以精准的知道方法何时出栈。
// 执行时创建栈帧,先入栈执行,执行完后出栈。
// 入栈 testA -> testB(),出栈 testB()->testA
public void testA(){
int a = 1; // 存储在局部变量表中。(线程私有)
User user = new User(); // 引用(句柄)存储在局部变量表(线程私有),对象本身存储于堆中(线程共享)。
testB();
}
3.本地方法栈:
服务于native方法。
三、线程共享
1.堆
虚拟机启动是创建,是虚拟机管理的最大内存区域。几乎所有的对象都在堆上分配内存。是垃圾收集器管理的主要区域(因为该区域比较大并且存有大量对象)。
堆可以细分:新生代,老年代;在细分有Eden空间、From Survivor空间,To Survivor空间。
堆中可以划分出多个线程私有的分配缓存区(Thread Local Allocation Buffer. TLAB)。该区域可以在用于精准分配对象内存,防止多线程情况下,内存覆盖。除了TLAB还有其他方式可以保证该点。
堆中区域内部的划分,目的是为了更高效的回收垃圾。、
举例理解TABL:
线程A,线程 B同时需要在堆上给对象分配内存.
1.如果没有TLAB:线程A 分配的内存,可能会被线程B分配的内存覆盖掉。(没有同步机制)
2.如果有TLAB:在各自的缓冲区分配内存。
2.方法区
存储被虚拟机加载的类信息、常量、静态变量、即时编译器(JIT)编译后的代码等数据。
2.1 运行是常量池:方法区一部分,用于存储编译器生成的各种字面量和符号引用。这部分内容在类加载后进入
常量池。
四、直接内存
不是虚拟机运行时的数据区部分。也不是虚拟机规范中的一部分。
该内存不会受到,java堆大小的限制,但是会受到本机内存的限制。
如jdk 1.4 加入的NIO,可以直接使用Native函数直接分配对外内存。
《深入理解java虚拟机》 (第二版) 学习之路