JVM内存结构
Java
我们都知道,java是一门广受认可的编程语言,除了它严谨的结构、面向对象编程之外,还有很多不可忽视的优点,如:
- 一次编写,到处运行
- 提供了相对安全的内存管理和访问机制
- 完善的应用程序接口
- …
那么,java为什么可以一次编写,到处运行呢?
这就是JVM的功效了。JVM,Java Virutal Machine,是java程序(java二进制字节码)的运行环境。通过JVM来实现屏蔽代码与底层操作系统的差异。
JVM结构
Java虚拟机在执行Java程序时,会把它管理的内存划分为不同的数据区域。这些数据区域各自有各自的用途,创建和销毁时间。有的内存区域随着虚拟机进程的启动一直存在,有的区域会依赖用户线程的启动和结束而建立销毁。
根据《JAVA虚拟机规范》,Java虚拟机所管理的内存包括下面几个部分:
我们写的java代码经过javac编译为二进制class文件,再通过类加载器加载到JVM去运行。类的基本信息,如类型信息、类的常量、静态变量等经过编译器编译后的代码会存放在方法区,运行时创建的实例对象存放在堆中,方法运行时,又会用到虚拟机栈、程序计数器和本地方法栈。在程序运行时,每行代码通过执行引擎中的解释器来执行,当检测到热点代码,又会通过即时编译器来进行编译优化来执行。当堆中的空间不足时,就会触发垃圾回收。还有一些代码java不方便实现,需要调用操作系统的方法,就会通过本地库接口来调用本地方法栈库来执行。
JVM运行时的数据区域
程序计数器Program Counter Register
程序计数器是JVM中一块比较小的内存空间,通过它来存放当前线程所执行的字节码的行号。虚拟机通过字节码解释器改变程序计数器的值来选取下一条要执行的字节码指令。
Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,程序计数器是线程私有的。
程序计数器是唯一一个在《JAVA虚拟机规范》中没规定任何OutOfMemoryErroring的区域。
如果正在执行的是本地方法,那么这个计数器的值就是空的
虚拟机栈JVM Stack
虚拟机栈描述的是Java方法执行的线程内存模型:每个方法在执行的时候,Java虚拟机都会为它创建一个栈帧来存放局部变量表、操作数栈、动态链接和方法出口等信息。每个方法从调用到执行完毕的过程,就会对应一个栈帧在虚拟机栈中从出栈到入栈的过程。
虚拟机栈也是线程私有的,它的生命周期与线程是相同的。
局部变量表
局部变量表存放了编译期可知的各种Java虚拟机基本类型、对象引用Reference类型(可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
这些数据类型在局部变量表中的存储空间以局部变量槽来表示,所需的内存空间在编译期完成分配。
可能出现的异常
StackOverFlow
线程请求的栈深度大于虚拟机允许的深度,会抛出StackOverFlow异常
OutOfMemoryError
如果Java虚拟机容量可动态扩展,当栈扩展时无法申请到足够的内存,会抛出OutOfMemoryError异常
HotSpot虚拟机的栈容量是不能动态扩展的,那么当线程申请栈空间时就失败,也会抛出OutOfMemoryError异常
本地方法栈Native Method Stacks
与虚拟机栈的所用基本一致,不过它是为虚拟机使用到底本地方法服务
堆
堆是虚拟机所管理的内存中最大的一块,用于存放对象实例。堆被所有的线程共享,在虚拟机启动时创建。
堆是垃圾收集器管理的内存区域,从回收内存的角度看,垃圾收集器大部分是基于分代收集理论设计的,所以堆经常与“新生代”,“老年代”,“永久代”,“Eden空间”,“FromSurvivor空间”,“ToSurvivor空间”等名词绑定,但这些区域划分仅仅是一部分垃圾收集器的共同特性或者设计风格,并非是Java虚拟机具体实现的固有内存布局,更不是《JAVA虚拟机规范》中对Java堆的划分。Java堆细分的目的只是为了更好的回收内存,或者更快的分配内存。
可能出现的异常
OutOfMemoryError
当Java堆中没有内存完成实例分配,并且堆也无法再扩展时,会抛出OutOfMemoryError异常
方法区Method Area
方法区用于存储被虚拟机加载到类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,也是各个线程共享的。
方法区是规范,在JDK8以前,永久代是HotSpot虚拟机对它的实现,JDK8以后,HotSpot开发团队废弃了永久代的概念,改为在本地内存中实现的元空间来代替,将类型信息等移到元空间中。
方法区的回收目标主要是针对常量池与类型的卸载。
可能出现的异常
OutOfMemoryError
当方法区无法满足新的内存分配需求,将会抛出OutOfMemoryError异常
常量池ConstantPool
经过编译后的java类
经过编译后的java类,也就是二进制字节码class文件包含:类基本信息、常量池、类的方法定义