zl程序教程

您现在的位置是:首页 >  后端

当前栏目

[二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义详解编程语言

2023-06-13 09:20:45 时间
JVM将这块内存按照功能进行了更细的划分,不过终究是一个规范,虚拟机的厂商在实现的时候仍旧有很大的自由度
接下来将会从两个方面  虚拟机可以处理的数据类型  以及  运行时的数据区的内存模型
整数类型(byte short int long char) 与浮点数(float  double)  与java语言中的值域在任何地方都是一致的,比如 取值范围表示含义
boolean编译后使用Java虚拟机中的int 数据类型代替,不过Java虚拟机支持boolean类型的数组,0表示false 1表示true
returnAddress 在Java语言中并不存在相应的类型 也就是程序员不能使用这个类型 ,而且也无法在程序运行期间更改
引用类型分为三种  类类型  接口类型 数组类型
值都是动态创建对象的引用
类类型的值是对类实例的引用
数组类型的值是对数组对象的引用
接口类型的值 是对实现了该接口的某个类实例的引用
另外还有一个特殊的引用null
byte 8位  有符号 二进制补码整数  默认值零(-2^7到2^7-1  包括两端的值在内)
char 16位 无符号 Unicode字符 默认值为null的码点 /u0000   (0 到2^16-1  包括两端的值在内)


内存结构内存结构组成部分 上面说过,程序运行,必然需要装载数据到内存 class文件会经由classLoader加载到JVM的运行时数据区域 JVM的内存结构为下图右侧部分 从图中可以看得出来 大致分为 方法区/堆/程序计数器/虚拟机栈/本地方法栈  五部分 接下来逐个进行介绍 ps:在抽象一点,逻辑上来说其实可以理解为堆/栈/程序计数器 三类  程序的运行 , 需要数据还需要方法,还需要说明从哪个指令位置开始执行 程序计数器就是指向要执行的指令地址,标志从哪个位置开始执行 栈是方法调用概念的具体化数据结构,描述了怎么执行 堆用于保存程序运行需要用到的数据对象等,描述了 执行什么,操作什么 image_5b84bfb7_4329    内存结构各部分详情 一个运行时的java虚拟机实例就是负责一个java程序的运行 启动一个Java程序一个虚拟机实例也就诞生,当这个Java程序关闭,这个虚拟机实例就销毁  每个Java程序都运行于他自己的Java虚拟机实例中 一个虚拟机实例中,堆和方法区是这个Java程序所有线程共享的 Java虚拟机栈和本地方法栈和程序计数器 是线程隔离独有的 下面所有说到的都是基于一个Java程序内的场景   image_5b84bfb8_34ac    方法区 方法区是可供各个线程共享的运行时内存区域,存储了每一个类的结构信息,如: 运行时常量池/字段和方法数据/构造函数和普通方法的字节码内容/类实例接口初始化时用到的特殊方法 方法区在虚拟机启动的时候创建 方法区也可以被垃圾收集 方法区大小不必是固定的 可以根据需要动态扩展  方法区空间也不必是连续的   具体存储的信息包括: 类型信息 类的全限定名 类型的直接超类全限定名

类型 类还是接口
方法信息
方法名
方法的返回类型
方法的参数数量和类型
方法的修饰符
方法的字节码(有方法体的)
操作数栈和该方法栈帧中的局部变量表  的大小(其实也还是class文件属性表的内容 静态的)
除了常量以外的所有类变量
类变量是所有类实例共享的,即使没有任何类实例,他也可以被访问,这些变量仅仅和类有关
所以
类变量总是作为类型信息的一部分存储在方法区
除了在类中声明的编译时常量外,虚拟机使用某个类之前 必须在方法区中为这些类分配空间
编译时常量指的是final声明以及用编译时已知的值初始化的类变量
这种和一般的类变量还不一样,每个使用编译时常量的类型,都会复制他的所有常量到自己的常量池中 或者嵌入到他的字节码流中
说白了对于这种值不变的,直接复制过去
类ClassLoader的引用/Class类的引用
每个类被装载后都必须跟踪他是由哪个类加载器加载的
对于每个被装载的类型,不管是类还是接口,虚拟机都会相应的为他创建一个java.lang.Class类的实例
而且虚拟机还必须以某种方式把这个实例和存储在方法区中的类型数据关联起来
运行时常量在Java虚拟机的方法区分配  加载类或者接口到虚拟机后,就创建对应的运行时常量池

另外类加载器以及当前Class对象这种运行时必须的信息,也被保存在方法区Java堆 一个java程序独占一个虚拟机实例,也就是每个java程序一个独立的堆空间 但是对于同一个java程序  堆是各个线程共享的运行时内存区域  是所有类实例和数组对象分配内存的区域

Java堆在虚拟机启动时就被创建了
每个方法在执行的同时都会创建一个栈帧  栈帧用于存储局部变量表 操作数栈 动态链接 方法出口等信息
当虚拟机调用一个方法时,从对应类的类型信息中得到此方法的局部变量表和操作数栈的大小(code 属性)
栈帧随着方法的调用而创建随着方法结束而销毁,无论方法正常完成还是异常完成都算作方法结束
局部变量表 长度由编译期决定,通过方法code属性提供 除了long和double 使用两个局部变量外,其余类型均为一个
操作数栈 后进先出,操作数栈最大深度编译期决定通过code属性保存提供 每个位置可以保存一个java虚拟机中定义的任意数据类型的值包括long double
栈帧数据区 除了局部变量和操作数栈外,还需要一些其他的数据,比如 常量池的入口信息,每当虚拟机要执行某个需要用到常量池数据的指令时
Java虚拟机实现可能会使用到传统的栈(通常称之为C stack) 来支持native方法(指 用Java以外的其他语言编写的方法)
这个栈就是本地方法栈 当Java虚拟机使用其他语言比如C语言,来实现指令集解释器的时候,也可以使用本地方法栈
可以使用native 函数库直接分配堆外内存,然后通过Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作
JVM运行时的内存结构,就是为了执行字节码文件,而将class文件中的信息加载到内存中的一个逻辑映射
以上所有的要求说明都是属于规范上的并不要求所有的实现与规范中定义的抽象元素完全的对应起来
但是只要他的外部行为是一致的,正确识别class文件,遵守class文件中包含的Java代码的语义,能够按照规定所需要呈现出来的行为结果
至于方法区到底应该如何分配空间,对象的内部表现形式如何,垃圾收集器如何运作,如何加载类都是由设计者来决定实现的.
比如 生鲜放到一个袋子,零食放到一个袋子,或者放置稍大商品的购物袋里面的缝隙处,放置一些小的商品
回到家需要做饭,可能你会把鱼拿出来放到盘子里,可能你会把青菜放到水槽中浸泡清洗,然后你可能会准备作料,洗锅准备做菜等等
可以理解你想要先做那道菜? 程序计数器((如果把想要做的菜都列一个清单,程序计数器就是从什么位置开始做,就是先做哪道菜)
具体的怎么做? 红烧还是清蒸?这些具体的行为封装在虚拟机栈的栈帧中  每次做一道菜就是入栈,做好了刷锅就是出栈
而每道菜所需要的调味料和配菜可能是独有的,不能乱放,这些就相当于栈帧中的局部变量和操作数栈