跨平台性是 Java 语言的重要特性,而这一特性本质上就是通过 JVM 虚拟机来实现的。下面就来通过深入学习 JVM 来进一步增加我们对 Java 这门编程语言的了解吧!(个人建议,最好能买来这本书去读一读,是非常有帮助的,当然,在看这本书之前,为了方便理解相关概念名词,可以先跟着某马程序员的视频课程大致过一遍 JVM 的内容体系:JVM 虚拟机基础入门视频教程,视频教程的全套笔记)
1. 运行时数据区
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里 面的人却想出来 —— 摘自(深入理解 Java 虚拟)。
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域 有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存 将会包括以下几个运行时数据区域,如下图所示。
下面我们来逐个学习一下运行时数据区的 5 个部分。(注意:JVM 运行时数据区可不能等同于 JVM 内存模型,对于不太熟悉 JVM 的初学者,面试的时候很容易把这两个概念搞混~)
1.1 程序计数器
程序计数器(线程私有),是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。它的核心作用就是:用于存储下一条所要执行的 JVM 指令的内存地址。
这里所说的线程私有,即不会出现并发安全问题,JVM 运行时数据区的 5 个部分中,只有 Java 堆、方法区是线程共享的,其他三个均为线程私有,后面还会提到这个知识点。
如下图:
Java指令执行流程:
每一条二进制字节码(JVM指令) 通过 解释器 转换成 机器码 然后 就可以被 CPU 执行了!
当 解释器 将一条jvm 指令转换成 机器码后 其会 向程序计数器 递交 下一条 jvm 指令的执行地址!
程序计数器在硬件层面 其实是通过 寄存器 实现的!
所以程序计数器的作用就是:用于保存JVM中下一条所要执行的指令的地址!
1.2 虚拟机栈
与程序计数器一样,虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是J ava 方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame),栈帧包含如下几个组成部分:
局部变量表:存放基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用(reference)等。这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的 long 和 double 类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小(这里说的“大小”是指变量槽的数量)。
操作数栈:也可以称之为表达式栈(Expression Stack),在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)。某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈,使用它们后再把结果压入栈,比如:执行复制、交换、求和等操作。操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
动态连接
方法出口等信息
每个线程运行需要的内存空间,这一空间被称为虚拟机栈(Frames),每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
每个栈由多个栈帧(Frame) 组成,对应着每个方法运行时所占用的内存,每个线程只能有一个活动栈帧,对应着当前正在执行的方法,当方法执行时压入栈,方法执行完毕后弹出栈。
如下图:
1.3 本地方法栈
一些带有native 关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法!
如图:
1.4 堆
堆是Java内存区域中一块用来存放对象实例的区域【几乎所有的对象实例都在这里分配内存】,Java 堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块 Java 堆是被所有线程共享的一块内存区域。
Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。
内存中的对象都需要考虑线程安全问题。
Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”(Garbage)。
-Xmx -Xms:JVM初始分配的堆内存由-Xms指定,64位的操作系统上,堆大小默认是物理内存的1/64。
1.5 方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
方法区只是一个抽象概念,其具体实现是通过以下 2 种方式:
永久代:JDK1.7版本之前。
元空间:JDK1.8版本之后。
永久代如下图所示:
元空间如下图所示:
由上图可以看出,1.6版本方法区是由PermGen永久代实现(使用堆内存的一部分作为方法区),且由JVM 管理,由Class ClassLoader 常量池(包括StringTable) 组成。
1.8 版本后,方法区交给本地内存管理,而脱离了JVM,由元空间实现(元空间不再使用堆的内存,而是使用本地内存,即操作系统的内存),由Class ClassLoader 常量池(StringTable 被移到了Heap 堆中管理) 组成。
2. 面试题案例
JVM面试题案例
后续会陆续更新,这本书的笔记记的差不多了,排版和格式需要花时间整理,文章都会同步到公众号上,也欢迎大家通过公众号加入我的交流qun互相讨论jvm这块的知识内容!