线程私有的内存区域:线程私有的内存区域与线程具有相同的生命周期,它们分别是:指令计数器、线程栈和本地线程栈。
线程四个共享区:四个共享区是所有线程共享的,在JVM启动时就会分配,分别是:方法区、 常量池、直接内存区和堆。
指令计数器:
线程执行的指令行号指示器。 每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
Java 虚拟机栈:
虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执
行的时候都会同时创建一个栈帧(Stack Frame ①)用于存储局部变量表、操作栈、动态
链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在
虚拟机栈中从入栈到出栈的过程。
本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其
区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则
是为虚拟机使用到的Native 方法服务
堆
Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的
一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的
唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java 堆中还可以细分为:新生代和老年代;
再细致一点的有Eden 空间、From Survivor 空间、To Survivor 空间等。如果从内存分配
的角度看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local
Allocation Buffer,TLAB)。
方法区
方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存
储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽
然Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-
Heap(非堆),目的应该是与Java 堆区分开来。
对于习惯在HotSpot 虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区
称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot 虚
拟机的设计团队选择把GC 分代收集扩展至方法区,或者说使用永久代来实现方法区而
已。对于其他虚拟机(如BEA JRockit、IBM J9 等)来说是不存在永久代的概念的。即
使是HotSpot 虚拟机本身,根据官方发布的路线图信息,现在也有放弃永久代并“搬家”
至Native Memory 来实现方法区的规划了。
运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有
类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool
Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放
到方法区的运行时常量池中。
Java 语言并不要求常量一定只能在编译期产生,也就是并非预置入Class 文件中常
量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特
性被开发人员利用得比较多的便是String 类的intern() 方法。
直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java
虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致
OutOfMemoryError 异常出现,所以我们放到这里一起讲解。
在JDK 1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)
与缓冲区(Buffer)的I/O 方式,它可以使用Native 函数库直接分配堆外内存,然
后通过一个存储在Java 堆里面的DirectByteBuffer 对象作为这块内存的引用进行
操作。这样能在一些场景中显著提高性能,因为避免了在Java 堆和Native 堆中来
回复制数据。
举例:
Object obj = new Object();
Object obj”这部分的语义将会反映到Java 栈的本地变量表中,作为一个reference 类型数据出现。
new Object()”这部分的语义将会反映到Java 堆中,形成一块存储了Object 类型所有实例数据值(Instance Data,对
象中各个实例字段的数据)的结构化内存
Java 堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
由于reference 类型在Java 虚拟机规范里面只规定了一个指向对象的引用,并没有
定义这个引用应该通过哪种方式去定位,以及访问到Java 堆中的对象的具体位置,因此
不同虚拟机实现的对象访问方式会有所不同,
主流的访问方式有两种:使用句柄和直接指针。
A) 使用句柄访问方式,Java 堆中将会划分出一块内存来作为句柄池,reference
中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息