JVM-内存模型详解

news2024/12/26 23:56:53

  JVM 把内存分为若干个不同的区域,这些区域有些是线程私有的,有些则是线程共享的,Java 内存区域也叫做运行时数据区,它的具体划分如下:在这里插入图片描述

虚拟机栈

  Java 虚拟机栈是线程私有的数据区,Java 虚拟机栈的生命周期与线程相同,虚拟机栈也是局部变量的存储位置。方法在执行过程中,会在虚拟机栈中创建一个 栈帧(stack frame)。每个方法执行的过程就对应了一个入栈和出栈的过程。在这里插入图片描述
在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。

一个线程中的方法调用链可能会很长,很多方法都同时处理执行状态。对于执行引擎来讲,活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method)。执行引用所运行的所有字节码指令都只针对当前栈帧进行操作。

局部变量表

局部变量表是变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。在java编译成class文件的时候,就在方法的Code属性的max_locals数据项中确定该方法需要分配的最大局部变量表的容量。

局部变量表的容量以变量槽(Slot)为最小单位,32位虚拟机中一个Slot可以存放32位(4 字节)以内的数据类型( boolean、byte、char、short、int、float、reference和returnAddress八种)

对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写。

reference类型虚拟机规范没有明确说明它的长度,但一般来说,虚拟机实现至少都应当能从此引用中直接或者间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据。

Slot是可以重用的,当Slot中的变量超出了作用域,那么下一次分配Slot的时候,将会覆盖原来的数据。Slot对对象的引用会影响GC(要是被引用,将不会被回收)。

系统不会为局部变量赋予初始值(实例变量和类变量都会被赋予初始值)。也就是说不存在类变量那样的准备阶段。

操作数栈

操作数栈和局部变量表一样,在编译时期就已经确定了该方法所需要分配的局部变量表的最大容量。

操作数栈的每一个元素可用是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型占用的栈容量为2。

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈 / 入栈操作。

在概念模型里,栈帧之间是应该是相互独立的,不过大多数虚拟机都会做一些优化处理,使局部变量表和操作数栈之间有部分重叠,这样在进行方法调用的时候可以直接共用参数,而不需要做额外的参数复制等工作。

动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中方法的符号引用为参数。

这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用(静态方法,私有方法等),这种转化称为静态解析,另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接

静态解析
静态解析的四种情形:
1.静态方法
2.父类方法
3.构造方法
4.私有方法(无法被重写)
5.final修饰的方法

动态连接
有些符号引用则是每次运行期间转化为直接引用,这种转换叫做动态链接.这体现为Java的多态性.
相关字节码指令:
1.invokeinterface—调用接口中的方法,实际上是在运行期决定的,决定到底调用实现该接口的那个对象的特定方法
2.invokestatic—调用静态方法
3.invokespecial—调用自己的私有方法,构造方法(以及父类的方法)
4.invokevirtual—调用虚方法,存在运行期动态查找的过程
5.invokedynamic—动态调用方法

方法返回地址

一个方法开始执行后,只有两种方式可以退出这个方法:
1.执行引擎遇到任意一个方法返回的字节码指令
传递给上层的方法调用者,是否有返回值和返回值类型将根据遇到何种方法来返回指令决定,这种退出的方法称为正常完成出口

2.方法执行过程中遇到异常
无论是java虚拟机内部产生的异常还是代码中throw出的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出的方式称为异常完成出口,一个方法若使用该方式退出,是不会给上层调用者任何返回值的。

无论使用那种方式退出方法,都要返回到方法被调用的位置,程序才能继续执行。方法返回时可能会在栈帧中保存一些信息,用来恢复上层方法的执行状态。

一般方法正常退出的时候,调用者的pc计数器的值可以作为返回地址,帧栈中很有可能会保存这个计数器的值作为返回地址。

方法退出的过程就是栈帧在虚拟机栈上的出栈过程,因此退出时的操作可能有:恢复上层方法的局部变量表和操作数栈,把返回值压入调用者的操作数栈每条整pc计数器的值指向调用该方法的后一条指令。

本地方法栈

  本地方法栈也是线程私有的数据区,本地方法栈存储的区域主要是 Java 中使用 native 关键字修饰的方法所存储的区域。

程序计数器

  程序计数器也是线程私有的数据区,这部分区域用于存储线程的指令地址,用于判断线程的分支、循环、跳转、异常、线程切换和恢复等功能,这些都通过程序计数器来完成。

方法区

  方法区是各个线程共享的内存区域

《深入理解Java虚拟机》书中对方法区(Method Aera)存储内容描述如下:它用于存储已被虚拟机加载的类型信息常量静态变量即时编译器编译后的代码缓存域信息方法信息等。

类型信息

对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:

1.类型的完整有效名称(全名=包名.类名);
2.类型直接父类的完整有效名(对于interface或是java.lang.Object,都没有父类);
3.类型的修饰符(public,abstract,final的某个子集);
4.类型直接接口的一个有序列表;

域(Field)(属性)信息

1.JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。

2.域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)

方法(Method)信息

JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:

1.方法名称;
2.方法的返回类型(或void);
3.方法参数的数量和类型(按顺序);
4.方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的某个子集);
5.方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外);
6.异常表(abstract和native方法除外)。
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引。

静态变量(non-final的类变量)

1.静态变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分。

2.类变量被类的所有实例共享,即使没有类实例时你也可以访问它。

静态变量(全局常量static+final)

被声明为final的类变量的处理方法则不同,每个全局常量在编译的时候就会被分配了。

常量池

实际上分为两种形态:常量池(静态常量池 Constant Pool Table)和运行时常量池(Runtime Constant Pool)。

常量池(静态常量池 Constant Pool Table),每个class一份,存在于字节码文件中。常量池中有字面量(数量值、字符串值)和符号引用(类符号引用、字段符号引用、方法符号引用),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
在这里插入图片描述

运行时常量池(Runtime Constant Pool),每个class一份,存在于方法区中(元空间)。当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询全局字符串池,也就是下面的StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

字符串常量池(堆中字符串常量池 String Pool),每个JVM中只有一份,存在于方法区中(堆)。全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)。

在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(用双引号括起来的引用而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。

方法区和永久代

说到方法区就联想到永久代,他们之间什么关系呢?

《Java虚拟机规范》只是规定了有方法区这个概念和它的作用,并没有规定如何去实现它。

不同的 JVM 上方法区的实现有所区别。在HotSpot上把GC分代收集扩展至方法区,或者说使用永久代来实现方法区

永久代是HotSpot的概念,方法区是Java虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现。其他的虚拟机实现并没有永久带这一说法。

各版本JVM方法区变化

版本说明
jdk1.6及以前有永久代(permanent generation),静态变量存放在永久代上
jdk1.7有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中
jdk1.8无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆

配置参数

JVM 1.7即以前:

#方法区初始大小
#默认是物理内存的1/64
-XX:PermSize=32m

#方法区最大大小,超过这个值将会抛出OutOfMemoryError异常:java.lang.OutOfMemoryError: PermGen
#默认是物理内存的1/4
-XX:MaxPermSize=64m

1.8以后使用了Metaspace,使用PermSize参数配置出现提示:
Java HotSpot™ Client VM warning: ignoring option PermSize=32m; support was removed in 8.0
Java HotSpot™ Client VM warning: ignoring option MaxPermSize=64m; support was removed in 8.0

Metaspace元空间

JDK1.8中,永久代已经不存在,存储的类信息、编译后的代码数据等已经移动到了MetaSpace(元空间)中,元空间并没有处于堆内存上,而是直接占用的本地内存(NativeMemory)。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。

Java8为什么要将永久代替换成Metaspace?

  1. 字符串存在永久代中,容易出现性能问题和内存溢出。
  2. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  3. 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

参数配置:

# 初始Metaspace元空间的大小
-XX:MetaspaceSize=128m 

# 最大Metaspace元空间大小,默认是没有限制
-XX:MaxMetaspaceSize=128m

  堆是线程共享的数据区,堆是 JVM 中最大的一块存储区域,几乎所有的对象实例都会分配在堆上。JDK 1.7后,字符串常量池从永久代中剥离出来,存放在堆中。

堆内存划分

Eden、S0、S1归为Young区(Young Gen),即新生代,执行new时大部分对象在此分配内存,经过一定GC次数(默认15次)后进入old区。

大部分对象在Eden区中生成,当Eden区满时,还存活的对象将被复制到Survivor区,当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到老年代。

Eden和survivor(S0、S1)默认比例是8:1:1。

old区(Old Gen)即老年代。
在这里插入图片描述

堆内存GC

在这里插入图片描述
堆是垃圾回收机制的重点区域。垃圾回收机制有三种:minor gc,major gc 和full gc

年轻代中存在的对象是死亡非常快的。存在朝生夕死的情况。

为了提高年轻代的垃圾回收效率,又将年轻代划分为三个区域,一个eden和两个S0(sunrvivor) S1(from)。

堆内存配置参数

#初始总堆内存,推荐和最大堆内存一样大
-Xms512m
#最大总堆内存
-Xmx512m

#新生代(Eden + 2*S)与老年代的比值
# 2:新生代占总堆大小1/3,老年代总堆大小2/3
# 3:新生代占总堆大小1/4,老年代总堆大小3/4
-XX:NewRatio=2

#S0,S1和Eden比值,S0,S1的比例固定为1/X ,X=SurvivorRatio+1+1
#默认Eden:S0:S1比值8:1:1
#配置6,Eden:S0:S1比值6:1:1
-XX:SurvivorRatio=8

执行以下配置验证:

-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms512m -Xmx512m -XX:NewRatio=4 -XX:SurvivorRatio=6

执行结果如下:

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 536870912 (512.0MB)
   NewSize                  = 107347968 (102.375MB)
   MaxNewSize               = 107347968 (102.375MB)
   OldSize                  = 429522944 (409.625MB)
   NewRatio                 = 4
   SurvivorRatio            = 6
   MetaspaceSize            = 134217728 (128.0MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 134217728 (128.0MB)
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 93978624 (89.625MB)
   used     = 14530664 (13.857521057128906MB)
   free     = 79447960 (75.7674789428711MB)
   15.461669240869073% used
Eden Space:
   capacity = 80609280 (76.875MB)
   used     = 14530664 (13.857521057128906MB)
   free     = 66078616 (63.017478942871094MB)
   18.026043651549795% used
From Space:
   capacity = 13369344 (12.75MB)
   used     = 0 (0.0MB)
   free     = 13369344 (12.75MB)
   0.0% used
To Space:
   capacity = 13369344 (12.75MB)
   used     = 0 (0.0MB)
   free     = 13369344 (12.75MB)
   0.0% used
tenured generation:
   capacity = 429522944 (409.625MB)
   used     = 0 (0.0MB)
   free     = 429522944 (409.625MB)
   0.0% used

2070 interned Strings occupying 158440 bytes.

NewRatio=4
老年代大小为总堆大小4/5,即512*4/5=409.6(结果输出:409.625MB);
年轻代大小为总堆大小1/5,即512*1/5=102.4(结果输出:102.375MB)。

SurvivorRatio=6
Eden占young区6/8,即102.375*6/8=76.78125(结果输出:76.875MB);
S0占young区1/8,即102.375*1/8=12.796875(结果输出:12.75MB)。

内存分配方式

  在类加载完成后,虚拟机需要为新生对象分配内存,为对象分配内存相当于是把一块确定的区域从堆中划分出来,这就涉及到一个问题,要划分的堆区是否规整。

  假设 Java 堆中内存是规整的,所有使用过的内存放在一边,未使用的内存放在一边,中间放着一个指针,这个指针为分界指示器。那么为新对象分配内存空间就相当于是把指针向空闲的空间挪动对象大小相等的距离,这种内存分配方式叫做指针碰撞(Bump The Pointer)

  如果 Java 堆中的内存并不是规整的,已经被使用的内存和未被使用的内存相互交错在一起,这种情况下就没有办法使用指针碰撞,这里就要使用另外一种记录内存使用的方式:空闲列表(Free List),空闲列表维护了一个列表,这个列表记录了哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

  所以,上述两种分配方式选择哪个,取决于 Java 堆是否规整来决定。在一些垃圾收集器的实现中,Serial、ParNew 等带压缩整理过程的收集器,使用的是指针碰撞;而使用 CMS 这种基于清除算法的收集器时,使用的是空闲列表

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

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

相关文章

word查看技巧:如何快速找到文档的修改痕迹

不知道大家在工作中有没有遇到过这类的工作场景:当初步拟好一份合作协议或是项目策划书后,发给老板或其他同事审阅和修订,通常会不断地来回修改文档。此时,如果你想要查看文档哪里被修改过?你会怎么操作?很…

SpringBoot集成Elasticsearch7.4 实战(二)

1、前言本篇文章主要讲的是:在Springboot环境下,利用JAVA环境操作索引,集成SpringBoot等相关知识2. SpringBoot集成开发工具,这里选择的是IDEA 2019.2,构建Maven工程等一堆通用操作,不清楚的自行百度。2.1. POM配置我这边选择 ela…

协程应用——aiohttp异步爬虫实战

aiohttp异步爬虫实战1. 案例介绍2. 准备工作3. 页面分析4. 实现思路5. 基本配置6. 爬取列表页7. 爬取详情页8. 总结1. 案例介绍 本例要爬取的网站是https://spa5.scrape.center/,数据量相对大,所以用到了异步爬虫,主要学习这种方法是如何提高效率的。网…

Maven学习(三):纯手撸一个Maven项目

纯手撸一个Maven项目一、创建Maven工程目录二、Maven项目构建命令三、插件创建工程1、创建java工程2、创建web工程3、对比java工程和web工程区别一、创建Maven工程目录 按照下图所示的结构创建项目所需文件夹: 在Demo.java文件内输入以下代码: package…

数据库被勒索删除,解决方法

突然数据库被黑了,有一条勒索信息: To recover your lost Database send 0.018 Bitcoin (BTC) to our Bitcoin address: bc1qe4yefrptv2k8shawu3h84j0n8kyvxfk4wwate5 After this, contact us by email with your Server IP or Domain Name and a Proof of Payment …

JavaScript中的严格模式

一.什么是严格模式 在ECMAScript5标准中,JavaScript提出了严格模式的概念: 严格模式是一种具有限制性的JavaScript模式,从而使代码隐式脱离了“懒散(sloppy)模式”;支持严格模式的浏览器在检测到代码中有严格模式时,…

卡方检验的基本原理详解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录一、卡方检验基本原理1. 1 χ2统计量计算公式1.2 理论频数如何计算?1.3 χ2值的结果如何理解?1.4 χ2检验的自由度如何理解?1.5 χ…

Arduino开发串口控制ESP8266 RGB LED

根据板卡原理RGB三色LED对应引脚:int LEDR12、int LEDG14、int LEDB13;设置串口波特率为115200Serial.begin(115200);源代码如下所示:/*名称:串口控制RGB亮灭实验功能:通过串口输入R、G、B三个字母来点亮对应的LED灯,关…

Java集合进阶——Map

一、Java Map集合详解 Map集合概述和特点 概述: 将键映射到值的对象 一个映射不能包含重复的键 每个键最多只能映射到一个值 Map接口和Collection接口的不同 Map是双列的,Collection是单列的 Map的键唯一,Collection的子体系Set是唯一的 Map集合的数据结构针对键有…

放假第三天

假期 # 生活 # 水文 咱们继续假期第三天的日常更文,没看上篇的铁子们我把地址贴在下面。 点我 虽然是假期,但我规划已久的睡懒觉流程却是一直执行不下去。这不今天早上八点我就起床了,当然起的早不是为了“卷”,而是吃早餐。说出…

Python操作 JWT(python-jose包)、哈希(passlib包)、用户验证完整流程

一、JWT简介 JWT是什么? JWT 即JSON 网络令牌(JSON Web Tokens)。 JWT(JSON Web Token) 是一种用于在身份提供者和服务提供者之间传递身份验证和授权数据的开放标准。JWT是一个JSON对象,其中包含了被签名的声明。这些声明可以是…

电脑开机出现绿屏错误无法启动怎么办?

电脑开机出现绿屏错误无法启动怎么办?有用户电脑开机的时候,突然出现了屏幕变成绿色的情况,而且上面有很多的错误代码。然后卡在页面上一直无法进入到桌面,重启电脑后依然无效。那么如何去解决这个问题呢?来看看具体的…

Java---Spring---SpringCache

SpringCache入门学习SpringCache介绍SpringCatch常用注解SpringCatch使用1.导入maven坐标2.配置application.yml3.在启动类上加入EnableCaching注解,开启缓存注解功能4.在controller的方法上加入Cacheable,CacheEvict等注解,进行缓存操作缓存穿透定义解决…

【Nginx】入门看这一篇就够啦,nginx 简介、安装、工作原理、工作方式、详解配置文件

目录 1、nginx 简介 2、nginx的工作原理 3、nginx 工作方式 4、nginx 安装 命令行安装 卸载命令 从源码构建 查看版本 测试启动 5、详解nginx配置文件 第一部分:全局块 第二部分:events块 第三部分:http 6、hosts 文件简介 1、…

解析Activity启动-窗口篇

解析Activity启动-窗口篇 在 解析Activity启动 前两篇文章中,我们分别专注于 堆栈 和 生命周期角度大致的过了一遍启动流程,而本篇会着重窗口的创建和显示流程,继续梳理Activity的启动流程 顺着前两篇文章的分析流程,我们知道和 …

DBCO高分子PEG_DBCO-PEG-Lipoic COOH_二苯并环辛炔-聚乙二醇-硫辛酸

DBCO-PEG-Lipoic acid“点击化学"一般由叠氮化物(azide)和炔烃(alkyne)作用形共价键,具有高效稳定,高特异性等优点。反应不受PH影响,能在常温条件下的水中进行,甚至能在活细胞中进行。DBCO…

第十三届蓝桥杯省赛 JAVA A组 - 矩形拼接

✍个人博客:https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 📚专栏地址:蓝桥杯题解集合 📝原题地址:付账问题 📣专栏定位:为想参加蓝桥别的小伙伴整理常考算法题解,祝大家…

Python学习中的六个技巧小结

1. 引言 “Beautiful is better than ugly.” 上述为著名的The Zen of Python的第一句话,也是有追求的python开发人员的信条之一。 所以我们的问题来了: 如何编写漂亮的Python代码? 本文重点通过九个示例向大家展示Python中的六个小技巧,以帮…

java后端-servlet超详细入门

java后端介绍今天我正式开始了一个新话题,那就是 Web。目前我主要会介绍后端。作为后端的老大哥 java,也有很多后端框架,比如大家耳熟能详的 spring 等。今天来带大家入门 servlet,不管是学生,刚毕业或是已经工作自学编…

【倍增】魔力小球

今天最后一篇,该睡了,怕猝死QwQ学校OJ上的一道模板题,去年不会做,今年还是不会做嘻嘻,还好最后调出来了,错的原因竟然是题目有歧义这个小球i的i是他喵的小球编号,不是id!出题人是懂出…