JVM内存模型基础

news2024/9/21 12:34:11

大家好,我是易安!

我们知道运行一个Java应用程序,我们必须要先安装JDK或者JRE包。这是因为Java应用在编译后会变成字节码,然后通过字节码运行在JVM中,而JVM是JRE的核心组成部分。

JVM不仅承担了Java字节码的分析(JIT compiler)和执行(Runtime),同时也内置了自动内存分配管理机制。这个机制可以大大降低手动分配回收机制可能带来的内存泄露和内存溢出风险,使Java开发人员不需要关注每个对象的内存分配以及回收,从而更专注于业务本身。但是懂得JVM内存分配以及调优才算是真正入门了Java这门语言,今天我们先看下JVM的内存模型。

JVM内存模型设计

我们先通过一张JVM内存模型图,来熟悉下其具体设计。在Java中,JVM内存模型主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈。

alt

JVM的5个分区具体是怎么实现的呢?

1. 堆(Heap)

堆是JVM内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由From Survivor和To Survivor组成。

在Java6版本中,永久代在非堆内存区;到了Java7版本,永久代的静态变量和运行时常量池被合并到了堆中;而到了Java8,永久代被元空间取代了。 结构如下图所示:

alt

2. 程序计数器(Program Counter Register)

程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。

由于Java是多线程语言,当执行的线程数量超过CPU核数时,线程之间会根据时间片轮询争夺CPU资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的CPU资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。

3. 方法区(Method Area)

很多开发者都习惯将方法区称为“永久代”,其实这两者并不是等价的。

HotSpot虚拟机使用永久代来实现方法区,但在其它虚拟机中,例如,Oracle的JRockit、IBM的J9就不存在永久代一说。因此,方法区只是JVM中规范的一部分,可以说,在HotSpot虚拟机中,设计人员使用了永久代来实现了JVM规范的方法区。

方法区主要是用来存放已被虚拟机加载的类相关信息,包括类信息、运行时常量池、字符串常量池。类信息又包括了类的版本、字段、方法、接口和父类等信息。

JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。在加载类的时候,JVM会先加载class文件,而在class文件中除了有类的版本、字段、方法和接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用。

字面量包括字符串(String a=“b”)、基本类型的常量(final修饰的变量),符号引用则包括类和方法的全限定名(例如String这个类,它的全限定名就是Java/lang/String)、字段的名称和描述符以及方法的名称和描述符。

而当类加载到内存中后,JVM就会将class文件常量池中的内容存放到运行时的常量池中;在解析阶段,JVM会把符号引用替换为直接引用(对象的索引值)。

例如,类中的一个字符串常量在class文件中时,存放在class文件常量池中的;在JVM加载完类之后,JVM会将这个字符串常量放到运行时常量池中,并在解析阶段,指定该字符串对象的索引值。运行时常量池是全局共享的,多个类共用一个运行时常量池,class文件中常量池多个相同的字符串在运行时常量池只会存在一份。

方法区与堆空间类似,也是一个共享内存区,所以方法区是线程共享的。假如两个线程都试图访问方法区中的同一个类信息,而这个类还没有装入JVM,那么此时就只允许一个线程去加载它,另一个线程必须等待。

在HotSpot虚拟机、Java7版本中已经将永久代的静态变量和运行时常量池转移到了堆中,其余部分则存储在JVM的非堆内存中,而Java8版本已经将方法区中实现的永久代去掉了,并用元空间(class metadata)代替了之前的永久代,并且元空间的存储位置是本地内存。之前永久代的类的元数据存储在了元空间,永久代的静态变量(class static variables)以及运行时常量池(runtime constant pool)则跟Java7一样,转移到了堆中。

那你可能又有疑问了,Java8为什么使用元空间替代永久代,这样做有什么好处呢?

官方给出的解释是:

  • 移除永久代是为了融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为JRockit没有永久代,所以不需要配置永久代。
  • 永久代内存经常不够用或发生内存溢出,爆出异常java.lang.OutOfMemoryError: PermGen。这是因为在JDK1.7版本中,指定的PermGen区大小为8M,由于PermGen中类的元数据信息在每次FullGC的时候都可能被收集,回收率都偏低,成绩很难令人满意;还有,为PermGen分配多大的空间很难确定,PermSize的大小依赖于很多因素,比如,JVM加载的class总数、常量池的大小和方法的大小等。

4.虚拟机栈(VM stack)

Java虚拟机栈是线程私有的内存空间,它和Java线程一起创建。当创建一个线程时,会在虚拟机栈中申请一个线程栈,用来保存方法的局部变量、操作数栈、动态链接方法和返回地址等信息,并参与方法的调用和返回。每一个方法的调用都伴随着栈帧的入栈操作,方法的返回则是栈帧的出栈操作。

5.本地方法栈(Native Method Stack)

本地方法栈跟Java虚拟机栈的功能类似,Java虚拟机栈用于管理Java函数的调用,而本地方法栈则用于管理本地方法的调用。但本地方法并不是用Java实现的,而是由C语言实现的。

JVM的运行原理

看到这里,相信你对JVM内存模型已经有个充分的了解了。接下来,我们通过一个案例来了解下代码和对象是如何分配存储的,Java代码又是如何在JVM中运行的。

public class JVMCase {

 // 常量
 public final static String MAN_SEX_TYPE = "man";

 // 静态变量
 public static String WOMAN_SEX_TYPE = "woman";

 public static void main(String[] args) {

  Student stu = new Student();
  stu.setName("nick");
  stu.setSexType(MAN_SEX_TYPE);
  stu.setAge(20);

  JVMCase jvmcase = new JVMCase();

  // 调用静态方法
  print(stu);
  // 调用非静态方法
  jvmcase.sayHello(stu);
 }

 // 常规静态方法
 public static void print(Student stu) {
  System.out.println("name: " + stu.getName() + "; sex:" + stu.getSexType() + "; age:" + stu.getAge());
 }

 // 非静态方法
 public void sayHello(Student stu) {
  System.out.println(stu.getName() + "say: hello");
 }
}

class Student{
 String name;
 String sexType;
 int age;

 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }

 public String getSexType() {
  return sexType;
 }
 public void setSexType(String sexType) {
  this.sexType = sexType;
 }
 public int getAge() {
  return age;
 }
 public void setAge(int age) {
  this.age = age;
 }
}

当我们通过Java运行以上代码时,JVM的整个处理过程如下:

1.JVM向操作系统申请内存,JVM第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间,根据内存大小找到具体的内存分配表,然后把内存段的起始地址和终止地址分配给JVM,接下来JVM就进行内部分配。

2.JVM获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小。

3.class文件加载、验证、准备以及解析,其中准备阶段会为类的静态变量分配内存,初始化为系统的初始值

alt

4.完成上一个步骤后,将会进行最后一个初始化阶段。在这个阶段中,JVM首先会执行构造器 方法,编译器会在.java 文件被编译成.class 文件时,收集所有类的初始化代码,包括静态变量赋值语句、静态代码块、静态方法,收集在一起成为 () 方法。

alt

5.执行方法。启动main线程,执行main方法,开始执行第一行代码。此时堆内存中会创建一个student对象,对象引用student就存放在栈中。

alt

6.此时再次创建一个JVMCase对象,调用sayHello非静态方法,sayHello方法属于对象JVMCase,此时sayHello方法入栈,并通过栈中的student引用调用堆中的Student对象;之后,调用静态方法print,print静态方法属于JVMCase类,是从静态方法中获取,之后放入到栈中,也是通过student引用调用堆中的student对象。

alt

了解完实际代码在JVM中分配的内存空间以及运行原理,相信你会更加清楚内存模型中各个区域的职责分工。

总结

今天我们主要深入学习了最基础的内存模型设计,了解其各个分区的作用及实现原理,内容篇基础,相信你很快就能掌握。

- END -

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/494724.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaWeb ( 五 ) Servlet

2.3.Servlet Servlet(Server Applet)是Java Servlet的简称。 是在服务器端执行的 , 用于响应客户端请求的Java类。HttpServlet 是使用java语言对http通信的实现。 2.3.1.Servlet声明 在 web.xml 中声明Servlet的请求url及对应的类路径 , 3.0版本后可以…

APSIM模型

随着数字农业和智慧农业的发展,基于过程的农业生产系统模型在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农田固碳和温室气体排放等领域扮演着越来越重要的作用。APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物生…

趣谈西工大电子实习物联网智慧交通系统

学习简介: 物联网智慧交通系统是电子实习中相当有趣的一个环节,可以在一定程度上弥补没有被分配到智能小车的遗憾。在这个模块当中,你将在老师的带领下以完成两个小任务为驱动,让自身能力在八个学时当中充分锻炼。 下面这两张图…

微信小程序商城搭建--后端+前端+小程序端

介绍: 前端技术:React、AntdesignPro、umi、JavaScript、ES6、TypeScript、 小程序 后端技术:Springboot、Mybatis、Spring、Mysql 软件架构: 后端采用Springboot搭配前端React进行开发,完成用户管理、轮播图管理、…

[MySQL / Mariadb] 数据库学习-Linux中安装MySQL,YUM方式

[Mariadb] 数据库学习笔记 在Linux中安装MySQL,YUM方式mariadb 介绍安装启服务初始配置修改密码 密码策略,默认策略是1show variables; 查所有变量show variables like "%变量%"; 查特定的变量参数临时:永久: MySQL基本操作连接SQL…

使用@PropertySource加载配置文件

1.PropertySource和PropertySources注解 1.1.PropertySource注解概述 PropertySource注解是Spring 3.1开始引入的配置类注解。通过**PropertySource注解可以将properties配置文件中的key/value存储到Spring的Environment中,Environment接口提供了方法去读取配置文…

ModStartCMS v6.3.0 电脑端在线充值,前端库升级

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用,支持后台一键快速安装,让开发者能快的实现业务功能开发。 系统完全开源,基于 Apache 2.0 开源协议,免费且不限制商业使用。 功能特性 丰富的模块市…

AMA 回顾|关于访问水晶铸造的一些调整建议

这是社区系列 AMA 的第一期。下周,我们将举行一场新的 AMA,讨论重新启动游戏的相关内容。 感谢大家在百忙之中参与这次活动。相信社区的每一位成员都在蓝精灵协会这个项目中投资了一些东西,比如时间、精力或者是金钱。蓝精灵协会团队在过去的…

docker打包流程

docker打包流程 1、使用docker前置准备: 电脑下载docker桌面版,以及开启虚拟机步骤:https://blog.csdn.net/qq_34905631/article/details/126573826下载docker桌面版 :https://docs.docker.com/desktop/install/windows-install…

swagger-codegen智能生成Python-unittest测试用例

简介:Swagger Codegen是一个开源项目,用于从OpenAPI规范(以前称为Swagger规范)文件生成服务器存根、客户端库和API文档。它支持多种编程语言和框架,包括Python、Java、Ruby、Go等。 历史攻略: sanic&…

快速打造高效代驾服务:代驾系统源码分享

要想快速打造高效代驾服务,选择一款优秀的代驾系统是非常重要的。本文介绍的代驾系统源码是基于PHP语言和MySQL数据库开发的,可以轻松地在Linux或Windows系统中部署。 首先,需要确保服务器环境符合系统的要求,包括PHP版本、MySQL版…

三范式(详解+例子)

第一范式(1NF):每一列都是不可分割的原子数据项(什么意思,每一项都不可分割,像下面的表格就能分割,所以它连第一范式都算不上) 分割后的样子 (它就是第一范式了&#xff…

crm项目bug小结

项目主要内容分析: 第一天完成了系统用户登录、退出、密码修改、全局异常、非法请求与记住我等系统基本功能。 项目的目录结构如图: 1 登录思路: ** * 1.参数校验 * 用户名 非空 * 密码 非空 * 2.根据用户名 查询用户记录 * 3.校验用户存…

搞懂 API ,API 中 URI 设计规范分享

API(Application Programming Interface)是现代软件开发中的一项关键技术,它为不同应用程序间提供了数据和功能交互的标准化方式。而 URI(Uniform Resource Identifier)作为 API 中的重要部分,其规范和良好…

BigCode开放性能超越Copilot的代码生成模型Starcoder

BigCode释出高效能程式码生成模型StarCoderBase,与为Python调校的StarCoder,效能超越GitHub Copilot初期版本所用的OpenAI code-cushman-001模型: BigCode昨晚发布了基于源代码和自然语言文本训练的编程语言生成模型StarCoder。其训练数据包…

JavaSE基础(五)—— 方法(定义、调用、重载)、return关键字

目录 一、方法定义、调用 1. 方法完整的定义形式、调用 2. 方法的其他定义形式、调用 二、方法使用的常见问题 三、方法案例 1. 定义方法的技巧、计算 1- n的和返回 2. 数组求最值案例改方法形式 四、方法调用的内存图 五、方法的参数传递机制 基本类型和引用类型的参…

携带信息的Ajax GET请求

前端页面代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>ajax get请求</title><script type"text/javascript">window.onload function () {document.getEle…

Spring管理数据库事务

Spring编程式事务和声明式事务的验证 1、工程目录pom.xml 2、在resources目录创建配置文件applicationContext_1.xmlapplicationContext_2.xmlapplicationContext_3.xmljdbc.properties 3、创建数据表accountaccount 4、创建dao类5、创建service类6、创建测试类7、实验结果图1 …

基于python的点云处理库总结

想对于PCL&#xff0c;python处理点云的库还是比较多的&#xff0c;下面对此进行简单的总结&#xff1a; 一、Open3D A Modern Library for 3D Data Processing&#xff0c;Intel出品&#xff0c;MIT协议。 Open3D是一个支持3D数据处理软件快速开发的开源库。Open3D使用C和P…

CSS - 垂直菜单(鼠标移入时显示右侧容器)

效果图 示例源码 <body><section class="content"><ul class=