JVM(Java Virtual Machine) 即Java虚拟机,是一种用于计算设备的规范,用于运行Java程序编译后得到的字节码文件(Class文件)
一。JVM的内存区域
1.程序计数器(Programing Counter Register)
用于选取需要JVM执行的字节码指令,最简单的一种方法就是通过修改程序计数器的值来达到选取下一条需要执行的字节码指令的目的。
每个线程都会有一个独立的程序计数器,即这块内存是属于线程私有(或者说线程隔离)的。若是执行的是Native修饰的方法,则计数器值为空(Undefined)。
此外,程序计数器所在的这块内存区域是唯一一个在JVM规范中没有任何OutOfMemoryError情况的区域
2.Java虚拟机栈(也就是我们常说的栈内存)
是线程私有的。每个方法在执行时都会创建一个栈帧(方法运行时的基础数据结构),用来存储局部变量、动态链接等。
其中,局部变量表中存放:基本数据类型(boolean、byte、char、short、int、long、float、double)和对象引用类型(reference类型,如存储引用的变量?)
在栈帧中,64位长度的long、double都会占用两个局部变量空间(Slot),其余数据类型均只占用1个Slot
虚拟机栈会有两种异常情况:(1)线程请求的栈深度大于虚拟机所允许的深度,会抛出 StackOverflowError 异常
(2)当虚拟机栈可以动态扩展时,若扩展时无法申请到足够内存,则会抛出 OutOfMemoryError 异常
3.Java堆(Java Heap)
是所有线程共享的。同时也是JVM管理内存中最大的一块。JVM规范规定:Java堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可。
JVM规范同时规定:所有对象实例以及数组都要在堆上分配。即Java堆是用来存储对象实例和数组。
Java堆也是垃圾收集器管理的主要区域,因此有别名"GC堆"(Garbage Collection Heap)。
当Java堆没有内存分配实例对象,且堆再也无法扩展时,会抛出 OutOfMemoryError 异常
4.方法区
是所有线程共享的。JVM规范中将方法区描述为堆的一个逻辑部分,但是却有一个别名叫 Non-Heap。
方法区是用来存储JVM加载的类信息(即类的模板?)、常量、静态变量、即时编译器编译后的代码等数据的。
和Java堆一样不需要连续的内存,且可以选择固定大小或扩展,还可以选择不实现垃圾回收(该区域的回收效率低下,且条件苛刻;但这区域回收是必要的)。
这区域的垃圾回收主要是针对常量池的垃圾回收和对类型的卸载。
当方法区无法满足内存分配需求时,会抛出 OutOfMemoryError 异常。
5.运行时常量池(Runtime Constant Pool)
方法区的一部分。
用于存放编译期生成的各种字面量和符号引用(如字符串常量);具备动态性,运行期间也可能将新的常量放入池中。
String类的intern()方法 ----- 去常量池中寻找当前字符串;若存在,返回找到与当前字符串值一样的字符串地址;若不存在,则将当前字符串存入常量池,再返回该地址。
当常量池无法再申请到内存时,会抛出 OutOfMemoryError 异常。
总结:线程隔离的有:程序计数器,Java虚拟栈。虚拟栈有一个 StackOverflowError 异常
线程共享的有:Java堆,方法区。方法区中有一个运行时常量池。
二。对象的创建
1.虚拟机遇到一条 new 指令时,首先进行类加载检查(是否能在常量池中找到类的符号引用、符号代表的类是否已被加载、解析、初始化等)。
2.通过类加载检查后,在Java堆中分配新内存。分配方式有:(1)指针碰撞:已用内存和空闲内存分置两边,中间设置一个指针作为分界点,每次通过移动指针来分配内存
(2)空闲列表:已使用内存和空闲内存相互交错,JVM维护一个列表,记录哪些内存块是可用的,分配时划分一块足够大的空间给对象
除了两种分配方式外,考虑到并发情况下的线程安全问题,提出了两种解决方案:
(1)同步处理