点击蓝字关注我们
预备知识
1. 计算机组成
计算机的CPU、内存、显卡等等属于计算机的硬件
常用的Windows、Linux属于计算机的操作系统
而java的虚拟机,也就是我们常说的 JVM是运行在操作系统之上的,与硬件没有直接联系,jvm也是java能够跨平台的根本原因。
JVM组成
1. Class Loader 类加载器
类加载器的作用是加载类文件到内存,比如编写一个 HelloWord.java 程序,然后通过 javac 编译成 class 文件,那怎么才能加载到内存中被执行呢?Class Loader 承担的就是这个责任,当然,不可能随便建立一个.class 文件就能被加载的,Class Loader 加载的 class 文件是有格式要求。
2. Execution Engine 执行引擎
Class Loader 只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由 Execution Engine 负责的。
执行引擎也叫做解释器 (Interpreter),负责解释命令,提交操作系统执行。
3. Native Interface 本地接口
本地接口的作用是融合不同的编程语言为 Java 所用,它的初衷是融合 C/C++ 程序,Java 诞生的时候是 C/C++ 横行的时候,要想立足,必须有一个聪明的、睿智的调用 C/C++ 程序,于是就在内存中专门开辟了一块区域处理标记为 native 的代码。
在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用 Socket 通信,也可以使用 Web Service 等等,不多做介绍。
4.Runtime data area 运行数据区
运行数据区是整个 JVM 的重点。我们所有写的程序都被加载到这里,之后才开始运行,下面会重点讲解运行时数据区。
总结:整个 JVM 框架由加载器加载文件,然后执行器在内存(运行时数据区和直接内存)中处理数据,需要与异构系统交互是可以通过本地接口进行。
运行时数据区域
Java的运行时数据区域分成四个部分:
堆
栈
PC计数器
方法区(持久代、元数据区)
1.堆
Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
成员变量名和值存储于堆中(静态变量名以及值存储于方法区的运行时常量池中,静态变量随类加载而加载,随类消失而消失),其生命周期和对象的是一致的。
2. 栈
栈分为本地方法栈和虚拟机栈
2.1 虚拟机栈
Java 虚拟机栈是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。
Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。(实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。
StackOverFlowError:若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 异常。
OutOfMemoryError:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 异常。
Java 虚拟机栈是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。
Java 方法有两种返回方式:return 语句和抛出异常。不管哪种返回方式都会导致栈帧被弹出。
2.2 本地方法栈
和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。
3. 程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。
另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,所以程序计数器也是线程私有。在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
4. 方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池:编译器生成的各种字面量和符号引用(编译期)存储在 class 文件的常量池中,这部分内容会在类加载之后进入运行时常量池。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
方法区也被称为永久代。很多人都会分不清方法区和永久代的关系:《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。
JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
1.8以前的内存结构
1.8及其以后
5.直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。
JDK1.4 中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
1.说一下 jvm 的主要组成部分?及其作用?
2.jvm 运行时数据区组成?
3.堆栈的区别?
4.运行时数据区哪些是线程共享,哪些是线程私有?
5.Java中成员变量、局部变量、静态变量、常量分别存储在那些内存区域中?
答案在上面的内容都可以找到,如果还有疑问,可以在公众号回复:基础200106
获取详细答案。
▇ 扫码关注我们 ▇
Java面试那点事
微信号 : javajob666