文章目录
关于虚拟机的一些概念
在将内存区域划分前,首先简单的了解以下虚拟机
虚拟机:
指通过软件模拟的具有完整硬件功能,运行在一个完全隔离环境中的完整计算机系统即就是
通过软件模拟完整的硬件系统
JVM虚拟机和VMware、Virtual的区别:
- VMware与VritualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多寄存器
- JVM是通过软件模拟Java字节码的指令集 ,JVM中主要保留了PC寄存器,对其他的寄存器都进行了剪裁。
1.Java内存区域的划分与内存溢出
JVM会在执行Java程序的过程中把它管理的内存划分为若干个不同的数据区域、这些区域各司其职,各有各的创建与销毁时间、有的区域随着JVM的启动而存在,有的区域则依赖用户线程的启动和结束而创建与销毁。
Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区
1.1 程序计数器
程序计数器是一块比较小的内存空间,可以看做是当前线程所执行字节码的行号指示器,也称作为PC寄存器
如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个native本地方法,这个计数器的值为空。
那么什么叫做线程私有呢?
由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令,因此为了切换程序后能恢复到正确的执行位置,每条线程都需要有独立的程序计数器,各条线程之间计数器互不影响,独立存储。我们把类似的这类区域称之为线程私有的内存。
程序计数器内存区域是唯一一个在JVM规范中没有规定任何内存溢出(00M out of memory)情况的区域
1.2 Java虚拟机栈
Java栈又称为虚拟机栈描述的是Java方法执行的内存模型。
Java栈中实际存放的是一个一个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表、操作数栈、指向当前方法所属类的运行时常量池的引用、方法返回地址和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。该区域也是线程私有的,它的生命周期与线程相同。
局部变量表: 用于存储方法中的局部变量。对于基本数据类型的变量,直接存储它的值。对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译时就确定好了,在执行期间,局部变量表的大小是不会改变的。
此区域一共会产生以下两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常
例如:递归无出口 - 如果虚拟机在动态扩展时无法申请到足够的内存空间,将会抛出OOM(out of memory)异常
例如:死循环创建对象
1.3 本地方法栈
本地方法栈和虚拟机栈的作用完全一样,区别只是本地方法栈为native方法服务,而虚拟机栈为JVM执行的java方法服务。
在Hotspot虚拟机中,本地方法栈与虚拟机栈是同一块内存区域
1.4 Java堆
Java堆是JVM所管理的最大的内存区域,Java堆是所有线程共享的一块区域,在JVM启动时建立,此内存区域存放的是对象实例。即所有的对象实例以及数组都要在堆上分配
Java堆是垃圾回收器管理的主要区域,因此很多时候可以称之为“GC堆”,java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。
如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。
1.5 方法区
方法区和Java堆一样,是多个线程共享的内存区域。它用于存储已被虚拟机加载的类信息,常量(static final),静态变量(static),及时编译器编译后的代码等数据。
在JDK8以前的HotSpot虚拟机中,方法区也被称为永久代(JDK8已经废弃永久代,被元空间取代)。移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制
永久代并不意味着数据进入方法区就永久存在,此区域的内存回收主要是针对常量池的回收以及对类型的卸载。
当方法区无法满足内存分配需求时,将抛出OOM异常
1.6 运行时的常量池
运行时的常量池是方法区的一部分,存放字面量与符号引用 例如:String直接赋值法定义的字符串直接入池
字面量:字符串JDK1.7后移动到堆中、final常量、基本数据类型的值。
符号引用:类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。
1.7 内存区域分布图