面试官:请介绍一下JVM内存结构


Java SE体系架构


面试官:请介绍一下JVM内存结构

Java SE(Java Platform,Standard Edition),Java 平台标准版,以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为 Java Platform,Enterprise Edition(Java EE)提供基础。

Java EE(Java Platform,Enterprise Edition)企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端 Java 应用程序。

Java ME(Java Platform,Micro Edition)为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。

JDK:Java 开发工具包,JDK 是 JRE 的超集,包含 JRE 中的所有内容,以及开发程序所需的编译器和调试程序等工具。

JRE:Java SE 运行时环境 ,提供库、Java 虚拟机和其他组件来运行用 Java 编程语言编写的程序。主要类库,包括:程序部署发布、用

户界面工具类、继承库、其他基础库,语言和工具基础库

JVM:java 虚拟机,负责 JavaSE 平台的硬件和操作系统无关性、编译执行代码(字节码)和平台安全性

虚拟机发展史

解释执行和编译执行(针对字节码的执行)

解释执行就是边翻译为机器码边执行、即时编译(编译执行)就是先将一个方法中的所有字节码全部编译成机器码之后再执行。

Hotspot 采用的是先解释执行,到了一定时机后热点代码(多次执行、循环等)再翻译成机器码

热点代码探测技术(通过执行计数器找到最有编译价值的代码,如果代码用得非常频繁,就会把这些代码编译成本地代码)。

JRockit 采取的方法是在执行 class 时直接编译为机器码(Java 程序启动速度会比较慢)

J9 和 Hotspot 比较接近,主要是用在 IBM 产品(IBM WebSphere 和 IBM 的 AIX 平台上),华为有的项目用的 J9。

谷歌:Google Android Dalivk VM:使用的寄存器架构,执行 dex(Dalvik Executable)通过 class 转化而来。

JVM 的整体介绍


面试官:请介绍一下JVM内存结构

首先class文件进行类加载,加载到JVM的运行时数据区然后执行引擎执行对应的机器码。

学习JVM的时候需要重点关注JVM的内存分配、垃圾回收。

JVM内存结构


面试官:请介绍一下JVM内存结构

运行时数据区域:


面试官:请介绍一下JVM内存结构

JVM在运行过程中会把它所管理的内存划分成若干不同的数据区域!

线程私有:程序计数器、虚拟机栈、本地方法栈

线程共享:堆、方法区


计算机的运行=指令+数据,指令用于执行方法的,数据用于存放数据和对象的。

虚拟机栈----执行 java 方法、

本地方法栈---执行本地方法、

程序计数器---程序执行的计数器

Java 中的数据:变量、常量、对象、数组相关。

程序计数器

较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。

如果线程正在执行的是一个 Java 方法,则指明当前线程执行的代字节码行数。

如果正在执行的是 Natvie 方法,这个计数器值则为空(Undefined)。

此内存区域是唯一一个不会出现 OutOfMemoryError 情况的区域。

本地方法栈

本地方法栈保存的是native方法的信息,当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法。

各虚拟机自由实现,本地方法栈 native 方法调用 JNI 到了底层的 C/C++(c/c++可以触发汇编语言,然后驱动硬件)

虚拟机栈

每个线程私有的,线程在运行时,在执行每个方法的时候都会打包成一个栈帧,存储了局部变量表,操作数栈,动态链接,方法出口等信息,然后放入栈。每个时刻正在执行的当前方法就是虚拟机栈顶的栈桢。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程。

栈的大小缺省为 1M,可用参数 –Xss 调整大小,例如-Xss256k

在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的 Code 属性之中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。

局部变量表:顾名思义就是局部变量的表,用于存放我们的局部变量的。首先它是一个 32 位的长度,主要存放我们的 Java 的八大基础数据类型,一般 32位就可以存放下,如果是 64 位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的 Object 对象,我们只需要存放它的一个引用地址即可。(基本数据类型、对象引用、returnAddress 类型)

操作数据栈:存放我们方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的 java 数据类型,所以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的,操作数栈运行方法是会一直运行入栈/出栈的操作

动态连接: Java 语言特性多态(需要类加载、运行时才能确定具体的方法,后续有详细的讲解)

返回地址: 正常返回(调用程序计数器中的地址作为返回)

三步曲:恢复上层方法的局部变量表和操作数栈、把返回值(如果有的话)压入调用者栈帧的操作数栈中、调整 PC 计数器的值以指向方法调用指令后面的一条指令、异常的话(通过异常处理器表来确定)

方法区(永久代(JDK1.7和以前)、元空间(JDK1.8))

方法区包含:类信息、常量、静态变量、即时编译期编译后的代码

类信息:类的完整有效名、返回值类型、修饰符(public,private...)、变量名、方法名、方法代码、这个类型直接父类的完整有效名(除非这个类型是 interface 或是 java.lang.Object,两种情况下都没有父类)、类的直接接口的一个有序列表。

几乎所有对象都分配在这里,也是垃圾回收发生的主要区域,可用以下参数调整:

-Xms:堆的最小值;

-Xmx:堆的最大值;

-Xmn:新生代的大小;

-XX:NewSize;新生代最小值;

-XX:MaxNewSize:新生代最大值;

例如- Xmx256m

直接内存

使用 Native 函数库直接分配堆外内存(NIO)

并不是 JVM 运行时数据区域的一部分,但是会被频繁使用(可以通过-XX:MaxDirectMemorySize 来设置(默认与堆内存最大值一样,也会出现 OOM 异常)避免了在 Java 堆和 Native 堆中来回复制数据,能够提高效率测试用例 JavaStack:设置 JVM 参数-Xmx100m,运行异常,因为如果没设置-XX:MaxDirectMemorySize,则默认与-Xmx 参数值相同,分配 128M 直接内存超出限制范围

深入辨析堆和栈

功能

 以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char 等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;

 而堆内存用来存储 Java 中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中;

 线程独享还是共享

 栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。

 堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

 空间大小

栈的内存要远远小于堆内存

栈溢出

参数:-Xss256k

java.lang.StackOverflowError 一般的方法调用是很难出现的,如果出现了要考虑是否有无限递归。

虚拟机栈溢出带给我们的启示:方法的执行因为要打包成栈桢,所以天生要比实现同样功能的循环慢,所以树的遍历算法中:递归和非递归(循环来实现)都有存在的意义。递归代码简洁,非递归代码复杂但是速度较快。OutOfMemoryError:不断建立线程。(一般演示不出,演示出来机器也死了)

建议:能消除递归尽量消除递归。


以上介绍了java虚拟机的一些入门的基础知识。如果对大家有用记得关注转发一下。


分享到:


相關文章: