JVM内存模型_gvm内存模型-程序员宅基地

技术标签: JVM  java  JVM内存模型  深入理解JVM  

Java在诞生之初就提出了"Write once,Run Anywhere"的口号,而这些都得益于JVM(Java Vritual Machine),可以提前在不同的运行环境(linux或windows等)上安装JDK之后,就可以让同一份代码在任何地方运行了。而这里的JDK(Java Development Kit)是Java语言、Java虚拟机和Java类库的统称。

一、JDK体系结构与Java跨平台特性

1.1 JDK体系结构

JDK是一个非常庞大的体系,里面包含了JVM、Java SE API(核心类库)、常用工具(javac等)以及Java语言等等一系列内容,其具体的体系结构图如下所示:

1.2 Java跨平台特性

Java的跨语言特性在文章开始已经提过了,通过在不同系统安装对应的JDK(主要是JVM)版本就可以实现,大概如下图所示:

二、JVM整体结构与内存模型

2.1 JVM整体结构

JVM称为Java虚拟机,作为一个虚拟机它首要的功能就是对应用程序的线程等分配内存空间,而JVM还要负责加载类、解析执行编译后的字节码文件,所以整个JVM主要就包含了运行时数据区、类加载子系统和字节码执行引擎。

大概的结构如下图所示:

在Java虚拟机外部,还有一些本地方法接口,这些接口也是JVM经常需要调用。

2.2 内存模型

Java虚拟机的运行时数据区显然是在内存中的,所以这一部分也成为JVM的内存模型,这里要与JMM(Java内存模型)区分开。

内存模型主要包含五部分的内容:堆、栈、本地方法栈、方法区(元空间)、程序计数器。

:JVM管理的最大一块内存空间,它是所有线程所共享的一块区域。在虚拟机启动的时候创建,该区域的唯一目的就是为了存放对象实例,几乎所有通过new创建的实例对象都会被分配在该区域。

栈(虚拟机栈):也可以称为虚拟机线程栈,它是JVM中每个线程所私有的一块空间,每个线程都会有这么一块空间。它的生命周期是与线程的生命周期是绑定的。虚拟机栈描述了Java中方法执行时的内存模型,即每个方法被执行的时候,线程都会在自己的线程栈中同步创建一个栈帧(Stack Frame),用于存放局部变量表、操作数栈、动态连接和方法出口等信息,每个方法从调用到完成的过程,就对应着一个栈帧在线程栈中从入栈到出栈的过程。

本地方法栈:本地方法栈与虚拟机栈的作用是相似,不同的是虚拟机栈为JVM执行的Java方法服务,而本地方法栈为JVM调用的本地方法服务。HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一。

程序计数器:只需要占用一小块的内存空间,每个线程都会有自己独立的程序计数器,主要功能就是记录当前线程执行到哪一行指令了,可以看作是当前线程所执行的字节码行号指示器。

方法区(元空间):在JDK 8之前,方法区也称之为永久代,这部分区域与堆一样,是所有线程所共享的,它主要用于存放被虚拟机加载的类型信息、常量、静态变量以及即时编译器编译后的代码缓存等数据。对于一个Class文件,除了版本、字段、方法、接口等描述信息外,还有常量池表,在上一篇文章《JVM类加载机制》中提到过,主要用于编译器生成的各种字面量和符号引用,而这部分内容在Class文件加载后是存放在方法区的运行时常量池中。这个运行时常量池自然还包括了字符串常量池,但需要注意的是,在JDK 7以后的版本中,字符串常量池和静态变量等被移至到了Java堆区,而到了JDK 8,抛弃了之前永久代的概念,通过在本地内存中实现了元空间(Meta-space)来代替永久代,并把JDK 7中永久代剩余内容(主要是类型信息)全部移至到了元空间

所以,方法区是使用直接内存来实现,这与堆是不一样的,也就是堆和方法区用的并不是同一块物理内存。

直接内存:直接内存并不是JVM运行时数据区的一部分,其分配不会受Java堆大小的限制。

下面以一个列子来演示JVM内存模型的各个部分,创建一个测试类如下:

public class Test {
    

    public int compute(){
    
        int a = 4;
        int b = 5;
        int c = a * b * 6;
        return c;
    }

    public static void main(String[] args) {
    
        Test test = new Test();
        int compute = test.compute();
        System.out.println(compute);
    }
}

将Test类编译之后,然后在控制台通过javap -c Test.class命令输出Class文件对应的字节码指令,下面主要分析compute()方法的字节码指令,结合JVM的内存模型。

public int compute();
	Code:
        0: iconst_4
        1: istore_1
        2: iconst_5
        3: istore_2
        4: iload_1
        5: iload_2
        6: imul
        7: bipush        6
        9: imul

下面是执行Test的main方法时,线程栈的模型,下面结合下面的模型和字节码指令来介绍运行时数据区的各个模块。
在这里插入图片描述

当执行Test类的main方法时,首先会将Test.class文件加载进JVM的方法区,主要有常量池和方法定义,而常量池中此时包含一个compute()的符号引用,而在执行main方法,当调用compute()方法时,会将compute()的符号引用变成该方法具体在方法区的内存地址(直接引用),因为这个过程是在程序运行时发生的,所以称为动态链接

在执行main()compute()方法时会分别在main线程栈中创建两个栈帧,因为main()先调用,所以其栈帧处于栈的底部。

通过compute()内存地址找到该方法包含的字节码指令就可以执行compute()方法了。

1、iconst_4表示将int类型数值4压入操作数栈中,此时compute()栈帧中的操作数栈中就只有一个数字4

2、istore_1表示将操作数栈顶的int类型数值放入到局部变量表的第二个槽位中(出栈),此时操作数栈就没有元素了。

注:局部变量表中,是以槽位为基本单位来存储数值,第一个槽位的索引下标为0,且存放的局部变量为当this引用

3、iconst_5表示将int类型数值5压入操作数栈中

4、istore_2表示将操作数栈顶的int类型数值放入到局部变量表的第三个槽位中

5、iload_1表示将局部变量表的第二个槽位中int类型的数值放入到操作数栈的栈顶

6、iload_2表示将局部变量表的第三个槽位中int类型的数值放入到操作数栈的栈顶

7、imul表示将操作数栈的元素全部出栈交给CPU进行乘法运算,然后再将运算结果压入操作数栈的栈顶

第七步完成后,此时操作数栈里面的数值只有一个20

8、bipush 6表示将单字节常量6放入到操作数栈的栈顶

9、imul将操作数栈中的int类型的元素20和6出栈交给CPU进行乘法运算,然后将计算结果120压入操作数栈的栈顶

10、istore_3表示将操作数栈顶的int类型数值放入到局部变量表的第四个槽位中

11、iload_3表示将局部变量表的第四个槽位中int类型的数值放入到操作数栈的栈顶

12、ireturn表示从当前方法返回int类型的数值,即操作数栈中的数值。

至此compute()方法就执行完成了,执行完之后,需要知道将返回到哪里去继续执行,这就是方法出口的作用,它会记录方法执行完之后,跳转到哪里。

注:对于这些常用的字节码指令可以参考周志明的《深入理解Java虚拟机》

三、JVM内存参数

JVM内存模型中的这些区域,都是有大小限制的,当然也可以通过JVM提供的参数来设置这些区域所占内存的大小。

补充:在JVM堆中的实例对象,会按照它们的年龄或大小分为新生代或老年代,而新生代的内存空间又分为Eden区和Survivor区,如下图所示:

至于为什么要这样划分内存空间,会在下篇文章介绍JVM的垃圾收集机制时详细介绍。

下面介绍这些常用参数的含义:

-Xss:表示每个线程栈的大小

-Xms:表示堆空间的初始可用大小,默认为物理内存的1/64

-Xmx:表示堆空间的最大可以大小,默认为物理内存的1/4(这个参数在应用程序中是一定要指定的)

-Xmn:表示新生代(年轻代)的大小

-XX:NewRatio:默认为2,表示新生代占年老代的1/2,占整个堆内存的1/3。

-XX:SurvivorRatio:默认为8,表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。

-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。

-XX:MetaspaceSize: 指定元空间触发Full Gc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。

由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般推荐将这两个值都设置为256M。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sermonlizhi/article/details/124774141

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签