一.JVM的概念
JVM是Java Virtual Machine的缩写,即java虚拟机。JVM是java语言平台无关性的关键,Java语言编译程序只需生成在JVM上运行的字节码,然后JVN将字节码解释成具体平台上的机器指令执行。这个特性使得Java能够“一次编译,到处运行”。而其他的高级语言在不同的平台上,至少需要编译成不同的目标代码。
二.JVM的内存模型
内存模型图一:
内存模型图二:
现在就针对以上的具体模块一个个理解。
1.堆(Head)
- JVM管理的内存中最大的一块,所有线程共享的一块内存区域,在虚拟机启动时创建
- 该区域的作用就是存放对象实例,几乎所有的对象都在这分配内存
- 垃圾回收的主要区域
- 从内存回收中分代收集的角度看,可分为新生代和老年代,新生代又分为:Eden、From Survivor和To Survivor
- 线程共享的java堆可能划分出多个线程私有的分配缓冲区,存储的都是对象实例,进一步划分的目的是为了更好的回收内存,更快的分配内存。内存空间可以是不连续的,只要是逻辑上连续即可(类似磁盘空间)
- 堆的大小可以是固定的,也可以是可扩展的。若堆中没有内存来完成实例分配,并且堆也无法再扩展时,将抛出OutOfMemoryError异常
2.Java栈(Java Stack)
- 描述的是Java方法执行的内存模型,每个方法的执行都对应一个栈帧的入栈和出栈过程。每个方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链表和方法出口等。
- 存储基本数据类型和对象引用
- 线程私有,每个线程都有各自的Java栈
- 局部变量表是在方法被执行的时候创建,随着栈帧的创建而创建。局部变量表所需的内存空间在编译期间完成分配,方法运行期间不会改变局部变量表的大小
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflow异常;如果虚拟机栈可以动态扩展,但扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常
3.本地方法栈(Native Method Stack)
- 本地方法栈类似于Java栈,区别就是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为Native方法服务
4.方法区(Method Area)
- 所有线程共享,堆的一个逻辑分区,也称为Non-Heap(非堆)
- 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 若GC分代收集扩展至方法区,那么该区域一般称为“永久代”,不同的虚拟机定义不同,之所以称为“永久代”,主要是该区域回收效率低,信息一般长期存在。对方法区回收的主要目标是常量池的回收和类型的卸载
-Java虚拟机对方法区的限制非常宽松,和Java堆一样,即不需要连续的内存,也可以选择固定的大小或者可扩展,还可以不实现垃圾回收
4.1.运行常量池
- 方法区中存放类信息、常量、静态变量和即时编译后的代码,其中常量存在于常量池中
- 类被Java虚拟机加载后,class文件中的常量就存在于方法区的运行时常量池中。在运行期间,可以向常量池中添加新的常量。比如String的intern()方法就可以在运行期间向常量池中添加字符串常量。但在jdk1.8后,将String常量池放到了堆中
- 运行时,常量池作为方法区的一部分,自然受到方法区内存的限制,当无法申请到内存时将抛出OutOfMemoryError异常。
5.程序计数器(Program Counter Register)
- 程序计数器是一块较小的内存空间,线程私有,是当前程序所执行的字节码的行号指示器。若线程执行的是Java方法,其对应的计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是Native方法,其对应的计数器值为空(Undefined)
- 唯一一个在Java虚拟机规范中没有OutOfMemoryError异常的区域
- 字节码解释器工作时,通过改变这个计数器的值来获取下一条需要执行的字节码指令,从而分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖此计数器来完成
6.直接内存
- 除Java虚拟机之外的内存,也可以被Java使用
- 在NIO中引入了一种基于通道和缓冲的IO方式。它可以调用本地方法直接分配Java虚拟机之外的内存,然后通过一个存储在Java堆中的DirectByteBuffer对象直接操作该内存,而无需将外面内存中的数据复制到Java堆中再操作,从而提高数据操作的效率
- 直接内存的大小不受Java虚拟机的控制,但当电脑内存不足时也会抛出OutOfMemoryError异常