JVM(运行时数据区)
- 内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。
- 1、线程私有:程序计数器、虚拟机栈、本地方法栈(随着线程的开始和结束)
- 2、线程共享:堆、方法区(随着虚拟机的启动和创建)
- 3、线程:在Hostspot JVM里,每个java线程都与操作系统的本地线程直接映射。当一个java线程准备好执行以后,此时一个操作系统的本地线程也同时创建;本地线程初始化成功,它就会调用java线程中的run()方法;java线程执行终止后,本地线程也会回收。
- 一、程序计数器:(线程私有)
- 1、作用:PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。通过改变PCR依次读取指令,实现代码的流程控制。
- 2、解释:
- 一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。分支,循环,跳转,异常处理,线程恢复等都需要依赖这个计数器来完成。
- 由于Java的多线程是通过线程轮流切换完成的,一个线程没有执行完时就需要一个东西记录它执行到哪了,下次抢占到了CPU资源时再从这开始,这个东西就是程序计数器,正是因为这样,所以它也是“线程私有”的内存。
- 如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。(正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。)
- 这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。(没有GC OOM)
- 二、虚拟机栈:(线程私有)
- 1、解释:
- 虚拟机栈描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,java虚拟机都会同步创建一个栈帧(Stack Ftame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。
- Java栈也称作虚拟机栈(Java Vitual Machine Stack),也就是我们常常所说的栈。事实上,虚拟机栈描述的是Java方法执行的线程内存模型。栈中的元素用于支持虚拟机进行方法调用,Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。
- 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
- 线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。所以在使用递归方法的时候容易导致栈内存溢出的现象,以及栈区的空间不用程序员去管理了(当然在Java中,程序员基本不用关系到内存分配和释放的事情,因为Java有自己垃圾回收机制),这部分空间的分配和释放都是由系统自动实施的。对于所有的程序设计语言来说,栈这部分空间对程序员来说是不透明的。
- 设置栈的大小:
- java虚拟机规范允许java栈的大小是动态的或者是固定不变。固定大小会溢出 StackOverflwError 异常;动态扩展会溢出 OutOfMemoryError。
- 设置栈:Run - Edit Configurations - 选择程序 - Configuration - VM options : -Xss245k(例)。
- 2、栈帧:
- 在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用(动态链接)(Reference to runtime constant pool)、方法返回地址(Return Address)和一些附加信息。
- 局部变量表(局部变量数组或本地方法栈):
- 定义:定义为一个数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用,以及returnAddress类型。
- 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题。
- 局部变量表所需要的内存空间在编译期间完成分配,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
- 操作数栈:
- 一个初始状态为空的桶式结构栈。由于 Java 没有寄存器,所有参数传递使用操作数栈。在方法执行过程中,会有各种指令往栈中写入和提取信息。JVM的执行引擎是基于栈的执行引擎,其中的栈指的就是操作栈。
- JVM底层字节码指令集是基于栈类型的,所有的操作码都是对操作数栈上的数据进行操作,
栈的深度在方法元信息的stack属性中。
- 动态连接:
- 每个栈帧中包含一个在常量池中对当前方法的引用,目的是支持方法调用过程的动态连接。
- 每一个栈帧内部都包含一个指向运行时常量池或该栈帧所属方法的引用,为了支持当前方法的代码能够实现动态链接。
- 在Java源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里, 那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
- 方法返回地址:
- 用于存放调用该方法的PC寄存器的值。当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
- 一个方法的结束退出有两种方式:正常执行退出、异常执行退出。无论通过哪种方式,在方法结束后都返回到该方法被调用的位置。方法正常退出时,返回地址为调用者PC计数器的值( 即调用该方法的指令的下一条指令地址 )。方法异常退出时,返回地址则是通过异常表来确定的,栈帧中一般不会保存这部分信息
- 正常执行退出和异常执行退出的区别在于:通过异常执行退出的不会给它的上层调用者产生任何的返回值
- 事实上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器值等,目的是使调用者方法继续执行下去
- 三、本地方法栈:(线程私有)
- 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
- 《Java 虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的 Java 虚拟机(如 HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
- 四、Java堆:(线程共享)
- Java堆(Java Heep)是虚拟机所管理的内存中最大的一块。Java对是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里 “几乎” 所有的对象实例都在这里分配内存。Java中的堆是用来存储对象本身的以及数组(当然,数组引用是存放在Java栈中的)。Java 堆是垃圾收集器管理的内存区域(GC 堆)
- 从回收内存的角度看:由于现代垃圾收强器大部分都是基于分代收集理论设计的,所以 Java 堆中经常会出现“新生代”、“老年代”、“永久代”、“Eden 空间”、“From Survivor 空间”、“To Survivor空间”等名词。这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个 Java 虚机具体实现的固有内存布局,更不是《Java虚拟机规范》里对Java堆的进一步细致划分。
- 从分配内存的角度看:所有线程共享的 Java 堆中可以划分出多个线程私有的分配缓冲区(Thread LocalAllocation Buffer,TLAB),以提升对象分配时的效率。不过无论从什么角度,无论如何划分,都不会改变 Java 堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将 Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。
- 五、方法区:(线程共享)
- 它用于存储被虚拟机加载的类型信息(类名、访问修饰符、字段描述、方法描述)、常量(运行时常量池)、静态变量、即时编译器编译后的代码缓存等数据。
- JDK8以前,当时的 HotSpot 虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区而已,这样使得 HotSpot 的垃圾收集器能够像管理 Java 堆一样管理这部分内存,省去专门为方法区编写内存管理代码的工作。JDK7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移至 Java 堆中,到了JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Meta-space) 来代替,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。
- 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
- 运行时常量池:
- 是方法区的一部分。用于存放编译期生成的各种字面量和符号引用,运行时常量池相对于类常量池另外一个特性就是具备动态性,运行期间可能将新的常量放入池中。
- 六、直接内存:
- 直接内存 (Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《 Java虚拟机规范》中定义的内存区域。
- 在JDK 1.4中新加人了NIO(New Input/Output)类,引人了一种基于通道(Channel)与缓冲区(Buffer)的I/0方式,它可以使用Native 函数库直接分配堆外内存,然后通过一个存储在Java堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和Native 堆中来回复制数据。
- 参考:
- https://javaedge.blog.csdn.net/article/details/116423830?spm=1001.2014.3001.5506
- JVM运行时数据区篇(虚拟机栈帧结构)_编程小吉的博客-CSDN博客^v76^pc_search_v2,201^v4^add_ask,239^v2^insert_chatgpt&spm=1018.2226.3001.4187
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/33331.html
如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!