一、内存划分
虚拟机规范中,将内存划分为六大部分,分别是方法区、JAVA堆、JAVA虚拟机栈、PC寄存器、本地方法栈以及运行时常量池。
其中运行时常量池是由方法区分配出来的一块区域。
二、JAVA虚拟机规范与JAVA虚拟机
JAVA虚拟机规范是一种对JAVA虚拟机实现的规范要求,由oracle制定,而我们平时常说的JAVA虚拟机一般是指的一种具体的JAVA虚拟机规范的实现。
我们最经常使用的JAVA虚拟机是hotspot
三、结构图
程序要运行,在内存中就要有数据空间。在JVM中这块数据空间被称为运行时数据区。
另一张图
四、内存区域详解
方法区(全局共享)
方法区保存了运行时常量池、字段信息、方法信息、构造方法与普通函数的字节码内容以及一些特殊方法。
方法区主要重要的有三块:1.类的常量池;2.指向当前类Class类对象的引用;3.指向当前类ClassLoader类对象的引用
方法区与Java堆除了存储的信息不一样之外,这一部分Java虚拟机规范不强制要求实现自动内存管理系统(GC)。
Java堆(全局共享)
Java堆随着JAVA虚拟机的启动创建,储存着所有对象实例以及数组对象,而且内置了“自动内存管理系统”,也就是我们常说的垃圾搜集器(GC)。
对于JAVA虚拟机如何实现垃圾搜集器,JAVA虚拟机规范没有明确的规定,也正因如此,我们平时使用的JAVA虚拟机中提供了许多种垃圾搜集器,它们采用不同的算法以及实现方式,满足多方面的性能需求。
JAVA虚拟机栈(线程独有)
JAVA虚拟机栈是在创建线程的同时创建的,用于存储栈帧,JAVA虚拟机栈也是线程独有的。它的基本单位是栈帧。
栈帧
栈帧保存了局部变量表、操作数栈、程序计数器,以及对当前类的运行时常量池的引用。
栈帧是随着方法的调用而创建,随着方法的结束而销毁,如果方法抛出异常,也算方法结束。
它又肩负着处理方法返回值、动态链接以及异常分派的任务
局部变量表
是在编译时期就写入了class文件当中。
可以将它理解为一个对象数组,而里面按照索引0到length-1分别对应于每一个局部变量。
如果是实例方法的局部变量表,第0个局部变量会是一个指向当前实例的引用,也就是this关键字,其余的局部变量则从索引1开始。
操作数栈它是一个后进先出(LIFO)栈,而它的长度也是在编译时期就写入了class文件当中,是固定的。它的作用就是提供字节码指令操作变量计算的空间,比如简单的,对于int a=9这句话来说,就需要先将9压入操作数栈,再将9赋给a这个变量。
PC寄存器(线程独有)
程序计数器(Program Counter Register),它记载着每一个线程当前运行的JAVA方法的地址,如果是当前执行的是本地方法,则程序计数器会是一个空地址(undefined)。
PC在每次指令执行后自增,维护下一个将要执行指令的地址。
在JVM模型中, 字节码解释器就是通过改变PC值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖PC完成。
本地方法栈(线程独有)
本地方法栈是一个传统的栈,它用来支持native方法的执行。
如果JAVA虚拟机是使用的其它语言实现指令集解释器的时候,也会用到本地方法栈。
如果前面这两种都未发生,也就是说如果JAVA虚拟机不依赖于本地方法栈,而且JAVA虚拟机也不支持native方法,则不需要本地方法栈。而如果需要的话,则本地方法栈也是随每一个线程的启动而创建的。
五、内存管理
内存管理分为内存分配和内存释放。大致分为两部分,一部分是全局共享,一部分是线程独有。
线程独有
对于线程独有的这部分内存,都是随着线程的启动而创建,而当线程被销毁时,内存也就随之释放。
这一部分内存,不需要垃圾搜集器的管理,而是JAVA虚拟机来主动管理,每当一个线程被创建的时候,JAVA虚拟机就会为其分配相应的PC寄存器和JAVA虚拟机栈,如果需要的话,还会有本地方法栈。相应的,当一个线程被销毁的时候,JAVA虚拟机也会将这个线程所占有的内存全部释放。
全局共享
全局共享的这部分内存(以下简称堆),内存分配主要是由程序员显示的使用new关键字来触发的,至于new出来的这部分内存在哪分配,如何分配,则是JAVA虚拟机来决定。而这部分内存的释放,则是由自动内存管理系统(以下简称GC)来管理的。
通常情况下,堆内存分配是要依赖于GC的策略与实现的,在分配的时候,就要考虑好到时候如何回收这部分内存。也是正因为如此,对于内存分配这一部分的讲解来说,我们必须得先了解内存是如何被回收的,才能更好的理解内存要怎么被分配。
参考资料