Java内存管理--运行时数据区

tech2022-12-16  107

运行时数据区

java虚拟机在执行java程序的时候,把管理的内存划分为若干个不同的数据区域,不同区域的用途、创建时间、销毁时间不同

java虚拟机把管理的内存(运行时数据区)划分为一下几个部分 程序计数器java虚拟机栈本地方法栈方法区堆
程序计数器
程序计数器是是一块较小的内存,可以看作当前线程所执行的字节码行号指示器。如果线程执行的是一个java方法,那么计数器记录的是该线程正在执行的字节码指令地址。如果正在执行的是一个本地方法,该计数器的值为空。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,因此程序计数器是线程私有的。程序计数器是内存区域中唯一一个没有OutOfMemoryError情况的区域。
java虚拟机栈
虚拟机栈与计数器一样是线程私有的,其生命周期与线程的生命周期相同。虚拟栈描述的是java方法执行的线程内存模型:每条线程都有自己的虚拟机栈,虚拟机栈中保存的元素就是各个栈帧(其实每个栈帧就是对一个方法的描述)。线程执行的每个java方法,虚拟机都会为该方法创建一个栈帧。一个栈帧包括:局部变量表(保存方法中的局部变量)、操作数栈(对变量进行计算时,将变量放入操作数栈进行计算)、动态连接、方法出口。。 局部变量表中存放编译时可知的数据,包括 基本数据类型(boolean、byte、char、short、int、float、long、double)对象引用(reference类型)returnAddress类型(返回地址类型) 以上的数据在局部变量表中是以局部变量槽来表示,其中64位的long、double类型数据占两个变量槽,其余的数据类型占一个槽。 局部变量表所需的内存空间在编译期完成分配(常量池中的方法表的Code属性中描述了方法的栈最大深度以及槽的个数),且在方法运行期间大小(槽的数量)不改变 虚拟机栈的内存区域有两类异常情况 (1)StackOverflowError异常:如果线程请求的栈深度大于虚拟机所允许的深度。 (2)OutOfMemoryError异常:如果java虚拟机栈容量可以动态扩展,当栈扩展无法申请到足够的内存。
本地方法栈

本地方法栈与虚拟机栈发挥的作用非常相似,只是虚拟机栈为虚拟机执行的java方法服务的,而本地方法栈为虚拟机使用的本地方法服务。本地方法可能是其他语言编写的程序。

java堆
java堆是所有线程共享的一块内存区域,在虚拟机启动时创建,且是虚拟机管理的内存最大的一块内存,java堆是垃圾收集器管理的内存区域。该区域的唯一目的就是存放对象实例。<<java虚拟机规范>>中指出“所有的对象实例以及数组都在堆上分配”。但是随着即时编译器的进步,尤其逃逸分析技术的强大,现在存在栈上分配、标量替换,所有说“所有”是值得商榷的。为了更好地回收内存或者更快的分配内存(存储对象实例),java堆可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。java堆可以物理上是不连续的内存空间,当是逻辑上是连续的。对于大对象(比如数组),是存储在连续的内存空间。主流的虚拟机把java堆设计成可扩展的,-Xmx :设置最大值 -Xms:设置最小值,当堆中以及没有足够的内存分配用于存储对象实例,那么抛出OutOfMemoryError异常
方法区
是线程共享的区域。方法区用于存储被虚拟机加载的类型信息(类的信息包含类的版本、字段、方法、接口等信息)、常量(即运行时常量池在方法区中)、静态变量、即时编译器编译后的代码缓存等数据。方法区不需要连续的内存,可以选择固定大小也可以选择可扩展,甚至可以选择不实现垃圾收集。方法区的垃圾回收目标主要针对常量池的回收以及类型卸载,该区域的垃圾回收效果比较难令人满意,但是有必要。当方法区不满足内存分配时,抛出OutOfMemoryError关于永久代:永久代是java堆的一部分,jdk8之前,用来实现运行时方法区。永久代与方法区不等价,HotSpot虚拟机设计将收集器的分代年龄扩展至方法区,或者说使用永久代来实现方法区,使得垃圾收集器可以像管理java堆一样管理方法区内存,省去了为方法区编写内存管理的代码。但是JDK8已经将永久代的概念废弃,用本地内存中实现的元空间作为方法区。永久代就是堆内内存,在jdk1.7及之前就是用永久代实现的方法区,故方法区在堆中,在1.7的时候,已经将永久代中的字符串常量池(移到了堆中)、静态变量(移到了堆中的Class对象末尾)等移出永久代。在1.8以及之后的版本中,方法区就是完全用本地内存(元空间)实现的,完全区别于堆

运行时常量池

运行时常量池是方法区的一部分。

Class文件中的常量表用于存储编译期生成的字面量、符号引用。常量表中的内容在类加载后放到了方法区的运行时常量池,符号引用翻译得到的直接引用也存储在运行时常量池中。

但是并不是说运行时常量池只存储Class文件中的常量,而过在运行时产生的常量也会放入运行时常量池中,比如String类的intern()方法(检查字符串常量池(存放字符串的引用,字符串对象还是存放在堆中)中是否存在String并返回池里的字符串引用;若池中不存在,则将其加入池中,并返回其引用。 这样做主要是为了避免在堆中不断地创建新的字符串对象)。String str = “Hello World”;这是字面量,在编译期间放入Class文件的常量表中,然后在运行期间放入运行时常量池。String str = new String(“Hello World”);这是一个String对象,对象是放如java堆中的。 +常量池中无法分配足够内存时,抛出OutOfMemoryError内存。

直接内存

直接内存不是虚拟机运行时数据区的一部分,但是这部分内存也会被频繁调用,也可能抛出OutOfMemoryError异常。jdk1.4中新加入NIO(New Input/Output)类,引入了一种基于通道和缓存的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样避免了java堆和Native堆中来回复制数据。直接内存不受到java堆大小的限制,但是受到本机总内存大小以及处理器寻址空间的限制。
最新回复(0)