Java虚拟机在运行的时候会将其所管理的内存划分为几个区域,由下图可以清楚地看到有的区域是线程共有的,有的区域是线程私有的
线程私有的内存区域
程序计数器
这部分是一块小的内存空间,它是用来记录当前线程所执行的字节码指令的地址。我们知道线程是占用CPU执行时间的基本单位,线程间的切换是采用时间片轮转调度的方式,当时间片再次切换回来的时候,就可以从当时切换出去的位置继续执行,这个位置的地址就是程序计数器来记录的。
但是我们需要注意的是,如果线程执行的是一个Java代码,程序计数器记录的就是正在执行的虚拟机字节码指令的地址;如果正在执行的是native方法,这个程序计数器是undefined地址(JVM获取不到native方法的是实现,通过通过系统指令去调度)。
Java虚拟机栈
每一个方法在执行同时,都会创建一个栈帧,这个栈帧(方法运行时基础的数据结构)存储的是局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用到调用结束就会对应一个栈帧在虚拟机栈中出栈,入栈的过程。
局部变量表存储的是编译期就已经知道的基本数据类型、对象引用和指向一条字节码指令的地址,由此可见局部变量表的的内存空间在编译期间就已经分配完成。
需要明确的是这个内存区域是线程私有的,不同的线程内,即使运行同一个方法,也是不同的内存;与方法有关,即使是一个线程,对于方法的递归调用也是生成不同的栈帧。
这个区域回存在两种异常:
1、如果线程请求的栈深度大于虚拟机所允许的一个深度将会抛出StackOverFlowError异常(方法的递归调用,每一次递归都是入栈的操作,当其中的栈帧数量太多,就会抛出这个异常)
2、虚拟机在动态扩展时无法申请到足够的内存空间,就会抛出内存溢出(应用系统中存在无法回收的内存空间或者使用的内存过大,导致程序运行要用的内存空间不够)OOM(OutOfMemoryError)异常(线程调用方法,创建方法该次调用的栈帧,内存不足就会报这个异常)
在这里区别一下内存泄露(Memory Leak),它是指程序中已经动态分配的堆内存由于一些原因程序未释放或者无法释放,造成系统内存的浪费。导致程序运行速度变慢或者程序直接崩溃。一般出现内存泄露的原因:长生命周期存活的对象,仍然持有不使用的对象的引用,导致不使用的对象垃圾对象无法回收,例如:使用长期存活的数据结构,数组时
本地方法栈
这个区域的作用和虚拟站的作用是一样的,区别一点就是本地方法栈是为虚拟机使用的native方法服务
线程共享的内存区域
堆
在Java虚拟机启动时创建,几乎所有的对象实例和数组都要在堆上进行内存的分配,倘若在堆上没有足够的内存空间进行对象实例的创建并且堆也没有办法扩展时会报出OOM
方法区/元数据区
方法区也是线程共享的,它是用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,此区域的内存主要是针对常量池的回收以及对类型的卸载。当方法区无法满足内存的分配是会报出OOM异常
直接内存
在JDK1.4中新加入的NIO类,引入了一种基于通道(Channel)和缓冲区(Buffer)的IO方式,可以使用native函数库直接分配堆外内存,然后使用一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用从而对这块内存进行操作,这样的话,就可以提高效率,因为避免了在Native堆和Java堆中来回的复制数据
直接内存的大小不会收到Java堆的影响,但是会受到本机总内存的大小的限制,因而这部分会抛出OOM异常
常量池
class文件常量池
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放
字符串常量池
这部分就是存储字符串对象,或者字符串对象的引用。具体详情:https://blog.csdn.net/qq_44723296/article/details/106210818
运行时常量池
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中,编译期和运行期产生的常量放在这里,z这里所说的常量就是:基本类型、包装类型(包装类不管理浮点型,整形只会管理-128到127)和String,在类加载的时候,会查询字符串常量池以保证运行时常量池所引用的字符串与字符串常量池中是一致的