JVM: Java Virtual Machine,Java虚拟机,包括处理器、堆栈 、寄存器等,是用来执行java字节码(二进制的形式)的虚拟计算机。
JVM由以下四部分组成(两个子系统和两个组件):
类加载器(ClassLoader)
执行引擎(Execution Engine)
运行时数据区(Runtime Data Area)
本地库接口(Native Interface)
(1)运行时数据区域我们在本文进行详解;
(2)类加载机制会在后续文章中依次分析,本文主要介绍运行时数据区域;
(3)执行引擎:
(4)本地方法库:
本地方法可以通过 JNI(Java Native Interface)来访问虚拟机运行时的数据区,甚至可以调用寄存器,具有和 JVM 相同的能力和权限。 当大量本地方法出现时,势必会削弱 JVM 对系统的控制力,因为它的出错信息都比较黑盒。对内存不足的情况,本地方法栈还是会抛出 nativeheapOutOfMemory。
(1)程序在执行之前先要把java代码转换成字节码(class文件);
(2)jvm首先需要把字节码通过类加载器(ClassLoader) 把文件加载到 运行时数据区(Runtime Data Area) ;
(3)字节码文件不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由CPU去执行;
(4)第三步过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能。
注:Java 虚拟机与 Java 语言没有什么必然的联系,它只与特定的二进制文件.Class 文件有关 。 因此无论任何语言只要能编译成.Class 文件,就可以被 Java 虚拟机识别并执行,比如Groovy、Kotlin。
JDK1.8之后的内存区域布局如下:
程序计数器(Program Counter Register)是一块较小的内存空间,是当前线程所执行的字节码的行号指示器。——内存空间小
字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。——计数执行
对于一个单核cpu(或者是一个内核)来说,只能同时执行一条指令,而JVM通过快速切换线程执行指令来达到多线程的,真正处理器就能同时处理一条指令,只是这种切换速度很快,我们根本不会感知到。为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。——线程私有,多线程的实现
如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。——无内存溢出
线程私有:Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同,与线程同时创建。线程的生命周期请参考我的另一篇文章:线程的生命周期。
虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame,是方法运行时的基础数据结构)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
(1)局部变量表
局部变量表是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量要显示初始化,没有默认值。
存放了编译期间可知的基本数据类型、对象引用类型(引用指针)和returnAddress类型(程序就是存储在方法区的字节码指令,指向特定指令内存地址的指针)。
(2)操作数栈
虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。
(3)动态链接
(2)在java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里;
比如:描述一个方法调用的另外的其它方法时,就是通过常量池中指向该方法的符号引用来表示,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
参考:https://www.zhihu.com/question/
知乎上参考到的理解:
线程切换恢复后也可以根据程序计数器(偏移量)结合这个引用,再次找到a方法在内存中上次执行到的位置,继续执行代码。
什么是符号引用:
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。
(4)方法返回地址
方法执行时有两种退出情况:
无论何种退出情况,都将返回至方法当前被调用的位置。方法退出的过程相当于弹出当前栈帧,退出可能有三种方式:
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。Sun HotSpot 虚拟机直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。
(2)从内存分配的角度来看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
作用:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
回收:垃圾收集行为在这个区域是比较少出现的,其内存回收目标主要是针对常量池的回收和对类型的卸载。
异常:当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。
运行时常量池相对于 Class 文件常量池的另外一个重要特征是具备动态性,Java 语言并不要求常量一定只有编译期才能产生,也就是并非预置入 Class 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是 String 类的 intern() 方法。(当调用 intern() 方法时,编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用。)
显然,本机直接内存的分配不会受到 Java 堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括 RAM 以及 SWAP 区或者分页文件)大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时,会根据实际内存设置 -Xmx 等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现 OutOfMemoryError 异常。
如图所示:
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!
到此这篇jvm内存模型 知乎(jvm1.8内存模型)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/qdvuejs/38835.html