JVM基础和Java内存区域
- JVM基础
-
-
- 什么是JVM
- JVM、JRE、JDK的关系与java程序的运行过程
- JVM的知识体系
-
- Java内存区域
-
-
- 运行时数据区域
- JAVA 方法的运行与虚拟机栈
-
-
- JAVA 方法的运行
- 虚拟机栈
-
- 栈帧执行对内存区域的影响
-
JVM基础
什么是JVM
JVM就相当于一个虚拟化的操作系统:
比如我们的windows、linux系统都是由CPU + 缓存 + 内存,而JVM是由执行引擎 + 操作数栈 + 栈、堆组成。
如下图:
JVM、JRE、JDK的关系与java程序的运行过程
JVM的功能是将class文件翻译成机器可以识别的代码,但是它并不能自己生成代码,所以就需要我们来编写代码,同时也需要很多的依赖类库,所以这个时候就需要用的JRE;
JRE除了包含了JVM之外,还包含了许多基础类库(就是我们的Jar包),比如说I/O、网络、线程等,这些类库就组成了java的运行时环境;
对于我们程序员来说,只有JRE是不够的,因为我们在编写完代码后,我们需要调试、打包,甚至是对代码进行反编译操作,那么这个时候我们就需要用到JDK了,JDK中包含了许多实用的小工具,就比如说avac(编译代码)、java、jar (打包代码)、javap(反编译<反汇编>)等,这些都是我们常用到的小工具。
所以就是JDK中包含JRE,JRE中包含了JVM。
那一个java文件在JDK、JRE、JVM中具体是怎么执行的呢?
1.使用JDK中的javac编译工具将.java文件编译成.class文件。
2.class文件及JRE中的类库同时进入JVM中的ClassLoader类加载器加载到内存中。
3.然后再经过字节码解释器或者JIT解释器编译为具体平台的机器码。
4.最后由执行引擎去调用物理机上面的硬件。
如下图
JVM的知识体系
包含内存结构、性能调优、JVM自身优化技术、执行引擎、监控工具、类文件结构、类加载、垃圾回收这几个模块,但是内存结构占据C为,因为这几个模块都无法脱离内存结构。
如下图
Java内存区域
运行时数据区域
定义:Java虚拟机在执行java程序的时候,会将它管理的内存划分为若干个数据区域
类型:分为线程共享区和线程私有区
线程共享区:所有线程都可使用,且只有一份
线程私有区:每一个线程都有单独属于自己的内存区域
在JVM中,主要内存主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈
直接内存:直接内存就是没有被虚拟机化的操作系统上的其他内存,所以直接内存不属于运行时数据区的一部分,但是会被频繁使用,就好比,我们的系统有8个G的内存,其中有3个G被JVM虚化了,所以还剩下5个G,而这5个G的内存是由JVM借助了一些工具来使用的,这个内存部分称之为直接内存。
如下图
JAVA 方法的运行与虚拟机栈
JAVA 方法的运行
虚拟机栈是线程运行 java 方法所需的数据,指令、返回地址。
在我们实际的代码中一个线程是可以运行多个方法的。
如下图
只是一段很简单的代码,就是起一个 main 方法,在 main 方法运行中调用 A 方法,A 方法中调用 B 方法,B 方法中运行 C 方法,我们把代码跑起来,线程 1 来运行这段代码, 线程 1 跑起来,就会有一个对应 的虚拟机栈,同时在执行每个方法的时候都会打包成一个栈帧。 比如 main 开始运行,打包一个栈帧送入到虚拟机栈。
运行结果如下图
C 方法运行完了,C 方法出栈,接着 B 方法运行完了,B 方法出栈、接着 A 方法运行完了,A 方法出栈,最后 main 方法运行完了,main 方法这个栈帧就 出栈了。 这个就是 Java 方法运行对虚拟机栈的一个影响。虚拟机栈就是用来存储线程运行方法中的数据的。而每一个方法对应一个栈帧。
虚拟机栈
栈的数据结构:先进后出(FILO)的数据结构, 虚拟机栈的作用:在 JVM 运行过程中存储当前线程运行方法所需的数据,指令、返回地址。 虚拟机栈是基于线程的:哪怕你只有一个 main() 方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生 命周期是和线程一样的。 虚拟机栈的大小缺省为 1M,可用参数 –Xss 调整大小,例如-Xss256k。
参数官方文档(JDK1.8):https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
栈帧:在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦方法完成相应的调用,则出栈。
栈帧大体都包含四个区域:(局部变量表、操作数栈、动态连接、返回地址)
1、 局部变量表: 顾名思义就是局部变量的表,用于存放我们的局部变量的(方法中的变量)。首先它是一个 32 位的长度,主要存放我们的 Java 的八大基础数据类 型,一般 32 位就可以存放下,如果是 64 位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的 Object 对象,我们只需要存放它的一个引用地址即可。
2、 操作数据栈: 存放 java 方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的 java 数据类型,所 以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的。 操作数栈本质上是 JVM 执行引擎的一个工作区,也就是方法在执行,才会对操作数栈进行操作,如果代码不不执行,操作数栈其实就是空的。
3、 动态连接: Java 语言特性多态(后续章节细讲,需要结合 class 与执行引擎一起来讲)。
4、 返回地址: 正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)
同时,虚拟机栈这个内存也不是无限大,它有大小限制,默认情况下是 1M。 如果我们不断的往虚拟机栈中入栈帧,但是就是不出栈的话,那么这个虚拟机栈就会爆掉。
如下图
这就是因为无限制的递归调用导致堆栈空间用尽,
并抛出Exception in thread “main” java.lang.StackOverflowError异常
程序计数器
较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。
程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。 由于 Java 是多线程语言,当执行的线程数量超过 CPU 核数时,线程之间会根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,或者是其
它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。
因为 JVM 是虚拟机,内部有完整的指令与执行的一套流程,所以在运行 Java 方法的时候需要使用程序计数器(记录字节码执行的地址或行号),如果 是遇到本地方法(native 方法),这个方法不是 JVM 来具体执行,所以程序计数器不需要记录了,这个是因为在操作系统层面也有一个程序计数器,这个 会记录本地代码的执行的地址,所以在执行 native 方法时,JVM 中程序计数器的值为空(Undefined)。
另外程序计数器也是 JVM 中唯一不会 OOM(OutOfMemory)的内存区域。
如下图
栈帧执行对内存区域的影响
在 JVM 中,基于解释执行的这种方式是基于栈的引擎,这个说的栈,就是操作数栈。
iconst:将一个常量加载到操作数栈
istore:将一个数值从操作数栈存储到局部变量表
iload:将一个局部变量加载到操作栈
iadd:加法指令
bipush:将一个常量加载到操作数栈
imul:乘法指令
ireturn:返回
对 class 进行反汇编 javap –c XXXX.class 字节码助记码解释地址:https://cloud.tencent.com/developer/article/1333540
转发于:
http://www.luckyiday.com/detail?id=818626639285063680