天意怜幽草,人间重晚晴。 —李商隐《晚晴》
运行时数据区域
JVM会在执行java程序的过程中把它管理的内存划分为若干个不同的数据区域。这些数据区域各有各的用处,各有各的创建与销毁时间,有的区域随着JVM进程的启动儿存在,有的区域则依赖用户线程的启动和结束而创建与销毁。一般来说,JVM所管理的内存将包含以下几个运行时数据区域。
线程私有区域:程序计数器、java虚拟机栈、本地方法栈
线程共享区域:java堆、方法区、运行时常量池。
线程私有区
程序计数器
程序计数器是一块比较小的内存空间,可以看作是当前线程所执行的字节码的行指示器。
如果当前线程正在执行一个Java方法,这个计数器记录的是正在执行虚拟机字节码指令的地址;如果正在执行行的是一个Native方法( 简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。),这个计数区值为空。
程序计数器内存区域是唯一一个在JVM规范中没有规范任何OOM情况的区域!
什么是线程私有?
由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定时刻,一个处理器(多核处理器则指的是一个内核)都只会处理一条指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储。我们就把类似这类区域称之为“线程私有”的内存。
Java虚拟机栈
虚拟机栈描述的Java方法执行的内存模型:每个方法执行的同时创建一个栈桢用于存储局部变量表、操作数栈、动态链接、方法出口(返回值)等信息。每一个方法从调用直至执行完成的过程,就对应一个栈桢在虚拟机栈中出栈和入栈的过程。生命周期与线程相同。
局部变量表 : 存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在执行期间不会改变局部变量表大小。
动态链接:动态的获取程序执行所需的资源文件等,Java是半编译,半解释型语言(个人理解)
此区域存在两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的深度**(-Xss设置栈容量)**,将会抛出StackOverFlowError异常。
- 虚拟机在动态扩展时无法申请到足够的内存,会抛出OOM(OutOfMemoryError)异常
本地方法栈
本地方法栈与虚拟机栈的作用完全一样,他俩的区别无非是本地方法栈为虚拟机使用的Native方法服务,而虚拟机
栈为JVM执行的Java方法服务。
在HotSpot虚拟机中,本地方法栈与虚拟机栈是同一块内存区域。
线程共享区
Java堆
Java堆(Java Heap)是JVM所管理的最大内存区域。Java堆是所有线程共享的一块区域,在JVM启动时创建,存放实例对象。JVM规范中规定:”对象实例以及数组都要在堆上分配“。
Java堆也是垃圾回收管理器的主要区域,因此很多时候也成为”GC堆“。根据JVM规范规定的内容,Java堆可处于物理上的不连续空间中。Java对在主流虚拟机中都是可拓展的(-Xmx设置最大值,-Xms设置最小值)
如果堆上没有足够的空间分配内存且无法在扩展时抛出OOM。
方法区
用于存贮已被虚拟机加载的类信息、常量、静态变量,即类被编译后的数据,。在JDK8以前的HotSpot虚拟机中,方法区也被称为"永久代"(JDK8已经被元空间取代)。
永久代并不意味着数据进入方法区就永久存在,此区域的内存回收主要是针对常量池的回收以及对类型的卸载。
JVM规范规定:当方法区无法满足内存分配需求时,将抛出OOM异常。
运行时常量池
运行时常量池是方法区的一部分,存放字面量和符号引用。
Each run-time constant pool is allocated from the Java Virtual Machine’s method area
字面量 : 字符串(JDK1.7后移动到堆中) 、final常量、基本数据类型的值。
符号引用 : 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。
Java堆溢出
内存泄漏 : 泄漏对象无法被GC
内存溢出 : 内存对象确实还应该存活。此时要根据JVM堆参数与物理内存相比较检查是否还应该把JVM堆内存调大;或者检查对象的生命周期是否过长。
虚拟机栈和本地方法栈溢出
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverFlow异常
- 如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OOM异常