您的位置 首页 java

Java内存模型一

本文介绍下 java 内存模型:

jdk 体系结构图

jdk由命令集(比如java、 javac )和 JRE (java运行时环境包含支撑java运行的核心类库(比如Collections)和 java虚拟机 jvm)。

跨平台

java代码一次编写,到处运行;其他语言,比如汇编,不同的操作系统平台,你开发的代码,可能要写多份,每个平台要写不一样的代码,但java语言不需要,写一份就行,在不同的平台上面都可以运行。

计算机可执行的代码是0101这种 二进制 机器码,同样的一份代码在windows下面,生成机器可执行的二进制码01010,在window平台下可能生成的是11110即同样的一份java代码最终到不同的操作系统平台下生成不一样的机器码。因为不一样的操作系统平台底层硬件指令集有区别,所以在不同平台实现同样的功能可执行的 二进制码 是不一样的。

java虚拟机是jdk内部的组成部分,下载jdk的时候选择不同操作系统平台版本的jdk,在不同平台下这个jdk里面也有对应的基于这个操作系统平台的 jvm 的实现,java跨平台主要是由java虚拟机实现的。

完整的java虚拟机由这3部分组成:类装载子系统、 字节码 执行引擎、运行时数据区。运行时数据区包括:堆、栈、本地方法栈、方法区、程序计数器。

首先通过javac指令码将java源码编译成字节码文件,再通过java命令来执行它,当通过java命令运行字节码文件的时候,java虚拟机就开始工作了,首先java虚拟机会通过它的第一个组成部分叫类装载子系统,把字节码文件给到java虚拟机的第二块组成部分:运行时数据区(即java虚拟机的内存区域),最终再通过java虚拟机的第三块组成部分:字节码执行引擎来运行内存区域里面的一些方法代码。

new出来的对象一般都会放堆中,虚拟机栈(线程栈)这块内存区域用来放局部变量。

程序有一个主线程要运行main方法 ,只要主线程开始运行代码,java虚拟机马上会给这个 线程 分配一块自己的虚拟机栈内存空间,也就是线程栈,这个程序在运行的过程当中会有一些局部变量,这些 局部变量 就是放在线程栈上面。又调用了compute方法,又会给compute方法分配一块自己专属的内存空间(栈帧内存空间),用来放这个局部方法内部的局部变量。

局部变量只在自己的方法作用域范围之内有效,所以通过栈帧把不同方法内存隔离开来,这就是栈帧,一个方法对应一块专属的内存区域,把这块区域叫栈帧内存区域。

栈内部的栈帧内存空间就是用的数据结构栈存放的,先进后出(FILO)的特点,线程开始运行,先分配一块线程栈内存,然后main方法开始运行,分配main方法对应的栈帧内存空间,main方法执行到一半的时候,调用compute方法,compute方法也分配自己的栈帧内存空间,但是注意 compute方法一运行完了以后,这一块内存空间会被释放掉,因为局部变量方法一结束就会被销毁即方法对应的帧帧内存空间在方法结束的时候会被销毁。

假设这是一个栈结构,分配内存就相当于入栈,释放内存就相当于出栈。

为什么java虚拟机要用栈这种数据结构来存储栈帧内存区域?因为它和方法的先后调用执行顺序是相符合的,先调用main方法先分配内存,然后调用compute方法分配内存,后调用的方法会先结束即先出栈,main方法后执行完也会销毁即出栈。main方法是先调用后执行,跟方法的嵌套调用顺序很符合,所以java的开发人员选择栈这种数据结构来存储栈帧内存区域。

像这些局部变量实际上就是放在一个个方法对应的栈帧内存空间里面的,不同的方法内部有它自己方法内部的局部变量,局部变量实际上是放在栈帧内部的一个表结构里面即局部变量表,局部变量表无非就是存储变量的存储结构,类比数组。

要想说明白操作数栈、动态链接、 方法出口,可能要看看java底层的字节码,因为单纯从java代码看不出太多java虚拟机底层执行代码的过程中执行了多少事情,因为这些东西是在java虚拟机程序执行过程当中的内存区域,所以得知道java虚拟机是怎么运行字节码的。

看下源码编译之后的字节码文件Main.class,

这里面每个 字符串 都有特定的含义,在 oracle 官方网站上有这些代码对应的手册,当然查找起来体验不太好,

但也可以通过javap这个jvm指令码指令(jdk自带的命令运行集)来查看,

javap -c 对代码进行反汇编生成另外一种jvm指令码,

 javap -c Math.class > math.txt
  

这个代码和上面的代码是等价的,都是java虚拟机生成的代码,但是这个代码的可读性更高。

越高级的语言代码量也少,越低级的语言代码量越多,再到底层就是汇编语言,最后到01110这种二进制机器码,越底层的语言代码量越大,代码量越大说明程序需要运行更多的细节逻辑。

j分析java虚拟机底层在运行的过程中内存的分配流转模型,当然得研究java虚拟机运行的代码,在oracle官方网站上都有对应的手册去查,看看java虚拟机到底做了什么事情,

先将int类型的常量1压入操作数栈,istore_1将int类型的值存入局部变量1当中,int类型的值就是常量1,

将1放到局部变量a里面来,这两行代码对应java的一行java代码(int a=1),

java虚拟机执行这一行jvm指令码的过程当中底层做什么事情:先会给a分配一个局部变量,刚猜测它好像是局部变量1,但不一定,先这么猜测,会先给局部变量a在局部变量表中分配一块内存空间,把1从操作数栈出栈,放到a对应的内存空间里面来。

这里解释下为什么有局部变量0,局部变量表的每个元素的下标分别是0、1、2、3,局部变量0相当于当前调用这个方法的对象this,局部变量1就代表着局部变量表某一个元素的下标。

接下来要执行iload_1、iload_2、iadd(1+2=3)、bipush 10(把10放入内存)、imul(3*10=30),把计算出来的结果30赋值给变量c,

给变量c分配内存空间,把30出栈放到变量c上,相当于给c赋值。

iload3把c这个局部变量值装载出来return返回给主方法。

在执行compute方法的时候找compute方法对应的指令码,动态链接就可以简单认为放的是compute方法对应的那些指令码的入口位置,即在方法区的入口地址,根据这个入口地址可以找到compute方法对应的所有的指令。

main执行compute肯定要找compute对应的代码,动态链接的概念是把符号引用转成直接引用,compute方法的名称就叫符号,符号的引用转变为直接引用(compute方法对应的内存地址),动态链接是jvm虚拟机底层很多c++代码实现的一块内容,牵扯到很多 c++ 的一些概念。

方法出口是compute方法运行完了之后,要回到调用compute方法的那行代码处继续执行,怎么知道要回到这行代码?调用compute方法的时候,实际上就已经把当前的线程位置保存到方法出口里面了,等compute方法执行完了之后,就可以根据方法出口里面记录的位置,往main方法的哪一行代码去执行的一个位置,包括返回值都通过方法出口返回到main方法里面去的。

文章来源:智云一二三科技

文章标题:Java内存模型一

文章地址:https://www.zhihuclub.com/188959.shtml

关于作者: 智云科技

热门文章

网站地图