JVM学习.01 内存模型

1、前言

对于C、C++程序员来说,在内存管理领域,他们拥有对象的“所有权”。从对象建立到内存分配,不仅需要照顾到对象的生,还得照顾到对象的消亡。背负着每个对象生命开始到结束的维护和管理责任。

对于JAVA程序来说,因为JVM虚拟机的加持,不再需要为每个对象去写配对的delete/free代码。交由虚拟机去管理内存,因而相对来讲不容易出现内存移除和内存泄漏的问题。不过也正是JAVA程序员把内存控制权交给了JVM,一旦出现了内存泄露和溢出的问题,修正起来会比较艰难,如果你不了解虚拟机的化。因而从事JAVA的程序员,多多少少需要了解JVM的内存模型,帮助我们更好应对JAVA内存问题。

2、JVM内存模型

很多Java开发人员会把Java内存区域划分为堆内存(Heap)和栈内存(Stack)。这种划分方式是直接继承C、C++程序的内存布局。在Java中实际内存区域划分会更复杂。

开篇一张图:

线程隔离的数据区,或称为“线程私有的内存”。他们的生命周期与线程相同。线程开辟的时候,会分配该内存空间,当线程被销毁,则这么部分内存空间也会随即释放。

2.1、 程序计数器

程序计数器为当前线程所执行的字节码的行号指示器。由于JVM的多线程是通过时间片轮转切换,依次分配处理器来执行的。因为在任何一个确定的时刻,一个处理器只能执行一条线程指令。当处理器被切换到另一个线程指令执行的时候,处理器需要记住当前指令中断的位置,以便下次执行的时候从当前中断位置恢复。该中断的位置成为指令字节码的行号。程序计数器就是用来存储该行号,因此程序的分支,循环,跳转,异常处理,线程恢复等都需要依赖这个计数器。

如果一个线程正在执行一个JAVA方法,则该计数器记录的是当前正在执行的虚拟机字节码指令的地址;

如果一个线程正在执行的是本地(Native)方法,则该计数器的值为空。

该内存区域也是唯一一个在《Java虚拟机规范》中没有规定任何OOM情况的区域。为线程私有

2.2、虚拟机栈

Java虚拟机以方法作为最基本的执行单元,“栈帧”则是用于支持虚拟机进行方法调用和执行的数据结构,也是虚拟机运行时数据区中的虚拟机栈的栈元素。

虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。虚拟机栈也是线程私有的。

例如举个简单的例子,我们同步将虚拟机栈内存放大:

// 有一段代码
double methodA() {
    int quantity = 10;
    double result = methodB(quantity);
    return result;
}

double methodB(int quantity){
    if(isVip()) {
        return quantity * _basePrice * 0.9;
    } else {
        return quantity * _basePrice * 0.98;    
    }
}

boolean isVip(){
    retrun _isvip == 1 ? true : false;
}

处理器在执行该段代码的时候,先执行methodA(),中间发现调用了methodB(),后面发现又调用了isVip()。此时方法methodA,methodB,isVip执行时的数据结构被称为栈帧。

则该线程的虚拟机栈模型如下:

  • 方法执行methodA方法,method方法对应的栈帧(栈帧1)被压入栈底位置,此时methodA为当前活动栈帧;

  • 当方法methodA调用methodB方法,此时methodB方法对应的栈帧(栈帧2)也被压入栈中,此时执行methodB方法;

  • 当方法methodB调用isVip方法,继续将isVip方法对应的栈帧(栈帧3)压入栈中;

  • 当isVip方法执行完毕,对应的isVip栈帧执行出栈操作,并将结果记录下来;

  • 当methodB方法执行完毕,同样对应的栈帧2执行出栈操作;

  • methodA执行完毕,对应的栈帧1执行出栈操作;此时虚拟机栈中没有任何的栈帧;当线程执行结束后,该虚拟机栈也会随即消亡(实际上是在等待被回收)。

试想一下:如果一个递归方法,且没有合适的条件退出。会导致死循环递归,那么最终该虚拟机栈也会被压爆。这时候虚拟机会抛出StackOverflowError异常。
StackOverflowError异常:指线程请求的栈深度大于虚拟机所允许的深度,将抛出该异常。
OutOfMemoryError异常:如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存,则会抛出该异常。(HotSpot虚拟机的栈容量是不可以动态扩展的,所以在此虚拟机上是不会出现虚拟机栈导致的OutOfMemoryError)。

2.2.1、局部变量表

是一组变量值的存储空间,用于存放方法的参数和方法内部定义的局部变量

局部变量是以变量槽(Slot)为最小单位。每个变量槽都应该能存放一个虚拟机基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用(reference类型或returnAddress类型)的数据。

当一个方法被调用时,JVM会使用局部变量表来完成参数值到参数变量列表的传递过程,即实参到形参的传递。如果执行的是实例方法(非static),那局部变量表中第0位索引的变量槽默认是用于传递方法所属对象实例的引用,在方法中可通过“this”来访问。

2.2.2、操作数栈

操作数栈是方法执行算数运算或调用其他方法进行参数传递时候的媒介。操作数栈也可以称为表达式栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据。

2.2.3、动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个方法的引用是为了支持方法调用过程中的动态链接。

Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或第一次使用时被转化为直接引用(称为静态解析)。另一部分将在每次运行期间转化为直接引用,这部分就称为动态连接。

2.2.4、方法出口

当一个方法执行后,要么正常调用完成,将返回值返回给上层调用者;要么异常调用完成,因为异常导致程序退出。

但是不管如何退出,在方法退出之后,程序都必须返回到最初方法调用时的位置,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。

方法退出的过程实际上等同于把当前栈帧出栈,所以退出时可能执行的操作有:

1、恢复上层方法的局部变量表和操作数栈;

2、把返回值(如果有的话)压入调用者栈帧的操作数栈中;

3、调整PC计数器的值以指向方法调用指令后面的一条指令等。

2.2.5、附加信息

其他附加信息。不过一般会把动态连接,方法返回地址,其他附加信息统一称为栈帧信息。

2.3、本地方法栈

本地方法栈与虚拟机栈的作用非常类似。只是虚拟机栈为Java方法服务,而本地方法栈为使用本地方法(Native)服务。HotSpot虚拟机通常直接把本地方法栈和虚拟机栈合二为一,统称为栈。同样本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

2.4、Java堆

对于Java应用程序,Java堆是整个虚拟机内存中最大的一块。是被所有线程共享的一块内存区域。Java中几乎所有的对象实例以及数组都在堆上分配。

因此堆是GC执行垃圾回收的重点关注对象。

堆空间的模型如下:

  • 方法区:详见2.5。

  • 老年代(Tenure / Old Gen):存储长期存活对象,老年代占堆空间的2/3。如果老年代内存满了,会触发Major GC。

  • 新生代(Young Gen):生命周期较短的对象,占对空间的1/3。其中新生代又分为Eden,From Survivor,To Survivor。

  • 伊甸空间(Eden):顾名思义,伊甸园为一切初始的地方。这里指对象的生命周期刚出生便是在这块内存区域。如果Eden空间不足以给新对象分配足够的内存,则会触发Minor GC对Eden进行垃圾回收,将不需要的对象销毁,剩余对象放进S0(From Survivor)区。如果再次触发GC,会将S0复制到S2。如果再次触发GC,存活对象从S2复制到S0。GC过程该空间会重复此步骤,直到对象存活周期经历过15次GC(默认15次,可配置)依然没有被回收,将会转移到老年代。

  • S0空间(From Survivor)/ S2空间(To Survivor):这两个成为幸存空间,Eden、S0、S2的内存占用比例默认为8:1:1。当新生代内存达到一定量时,如果直接进行垃圾回收(清理)会带来空间碎片问题。因此当进行清理之前,会将存活的对象放进S0和S2区域,有助于垃圾回收和清理。

为什么Eden、S0、S2的内存占用比例默认为8:1:1?
IBM公司研究表明,新生代中的对象约98%生命周期都是很短的。8:1:1是基于大量实验和数据收集分析统计之后的比较合理的比例。

Minor GC / Young GC:新生代GC
Major GC:老年代GC,对于高响应要求的系统,需要尽量减少Major GC,会导致响应超时
Full GC:清理整个Heap空间,包括新生代,老年代,永久代

为什么要把堆空间进行分代?不分代不能工作吗?
其实分代的意义是为了优化垃圾回收(GC)的性能,简单理解就是分而治之。分代以后对部分需要清理对象只需要小范围进行回收即可,无需扫描整个堆空间。不过后面的G1垃圾收集器开始,取消了内存分代,取而代之的是每个平等的region。

一个对象创建中堆空间的内存申请和分配流程大致如下:

此外JVM提供了一些操作对空间的参数选项,常见的有:

参数

描述

-Xms

堆内存初始大小

-Xmx

堆内存最大允许大小

-Xns

新生代内存初始大小

-Xmn

新生代最大允许大小

-XX:SurvivorRatio=8

年轻代中Eden区与Survivor区的容量比例值,默认为8,即8:1

-Xss

线程栈内存大小。JDK1.5后默认每个为1M,减少该值能生成更多线程

2.5、方法区

方法区也是线程共享的内存区域,用于存储已被JVM加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。别名也叫“非堆”,目的是与Java堆区分开。

1、类型信息:类class,接口interface,枚举enum,注解annotation

2、字段信息(域信息):域名称,信息,类型的修饰特征符public, abstract,final......

3、方法信息:返回类型void等,参数列表,方法修饰特征public, protected

/**
 * Student:方法区
 * stuInstance: 栈区
 * new Student(): 堆区
 */
Student stuInstance = new Student();

说到这里,很多人会把方法区称为“永久代”,或者进行等价。本质上不是的,起初HotSpot设计团队选择把分代设计扩展至方法区,或者说用永久代来实现方法区,这样做的目的是HotSpot的GC回收器能够像Java堆一样管理这部分内存,就不用单独为方法区编写一个专门的内存管理工作。

JDK8之后废弃了永久代,改为元空间(Meta Space)。元空间与永久代类似,最大的区别是元空间直接使用本地内存,而不是JVM。因此JDK8过后,元空间就不再会出现OOM问题。

2.6、运行时常量池

运行时常量池是方法区的一部分。class文件中除了有类的版本,字段,方法,接口等描述信息以外,还有常量池表,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

常量池是方法区的一部分,当然如果无法申请到内存时,也会抛出OutOfMemoryError。

3、直接内存

直接内存并不是JVM的内存区域,属于操作系统本身的内存。JDK1.4加入的NIO类,引入了Channel与缓冲区Buffer。它可以直接使用Native函数库直接分配直接内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用来进行操作,可以显著提高性能。

为什么这里要讲直接内存?直接内存虽然不受到Java堆的限制,但是收到了操作系统总内存大小以及处理器寻址空间的限制。 通常我们在用-Xmx设值堆大小信息时,会经常忽略直接内存;有可能使得内存区域大于物理内存限制,而导致动态扩展时出现OOM异常。

直接内存既然不属于Java内存,那么自然也JVM GC也无法回收他。如果需要回收,需要主动调用Unsafe的freeMemory方法。

可以通过-XX:MaxDirectMemorySize来指定直接内存的容量大小,如果不指定,默认与Java堆的最大值一致。

直接内存导致内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常情况,如果发现内存溢出之后产生的Dumo文件很小,而程序中又直接或间接使用了Directmemory(典型的间接使用就是NIO),那就可以考虑重点检查一下直接内存方面的原因。

4、小结

JVM专栏第一篇。明白了JVM的内存模型,对于JVM内存的一些问题处理应该会更加得心应手(面试唬人)。

参考资料:《深入理解Java虚拟机》 - 第三版

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第十四届蓝桥杯三月真题刷题训练——第 15 天

目录 第 1 题:斐波那契与7 问题描述 答案提交 运行限制 代码: 第 2 题:小蓝做实验 问题描述 答案提交 运行限制 代码: 第 1 题:斐波那契与7 问题描述 斐波那契数列的递推公式为: FnFn−1Fn−2​, 其中 F1F21…

【C#进阶】C# 索引器

序号系列文章13【C#进阶】C# 特性14【C#进阶】C# 反射15【C#进阶】C# 属性文章目录前言1、索引器的概念2、索引器的定义3、索引器的基本使用4、索引器的重载5、接口中的索引器6、属性和索引器之间的比较7、索引器的适用场景结语前言 🍂 Hello大家好啊,我…

News乐鑫科技亮相德国嵌入式展 Embedded World 2023!

3 月 14 日,德国纽伦堡嵌入式展 Embedded World 2023 火热启幕。本届 Embedded World 主题为 “embedded. responsible. sustainable”,乐鑫科技 (688018.SH) 携众多 AIoT 科技成果亮相展会,致力于打造更智能、更互联、更绿色的物联网未来。…

Linux - 进程地址空间

引入在学习C语言的时候,内存包括栈区、堆区、静态区这个布局是内存吗? 不是!! 这是进程地址空间!下面测试一下:11540是bash进程我们修改一下源程序,在观察下结果发现父进程的g_value的值不变&am…

TVS和稳压管的相同点和不同点

大家好,我是记得诚。 文章目录 介绍相同点不同点介绍 TVS和稳压管都是电路中很常用的电子元器件,都是二极管的一个种类。 TVS二极管全称是Transient voltage suppression diode,也叫瞬态电压抑制二极管。 稳压二极管英文名字Zener diode,又叫齐纳二极管。 关于稳压二极…

微信小程序项目实例——扫雷

今日推荐💁‍♂️ 2023许嵩演唱会即将到来🎤🎤🎤大家一起冲冲冲🏃‍♂️🏃‍♂️🏃‍♂️ 🔮🔮🔮🔮🔮往期优质项目实例&#x1f52e…

win10下使用docker运行部署nginx,mysql

一、docker的步骤:1.进入docker官网下载安装包2.打开控制面板 - 程序和功能 - 启用或关闭Windows功能,勾选Hyper-V,然后点击确定即可,如图:3.重新启动电脑4.启动Docker在桌面找到Docker for Windows快捷方式&#xff0…

学习PCB设计前的知识扫盲

参考: 走进工厂:PCB线路板是如何制造出来的 学习PCB设计前的知识扫盲,新手向,越新手越好! 下一步可继续学习简易的PCB绘制: 如何快速阅读芯片数据手册(初学者和外行进) 【完结】极简…

【Java】看看关于代码块的这些知识,你掌握了多少?

作者:努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:算法、数据结构、Java等相关知识。博主主页: 是瑶瑶子啦所属专栏: Java岛冒险记【从小白到大佬之路】;该专栏专注于Java相关知识&#xff0c…

文心一言,通营销之学,成一家之言,百度人工智能AI大数据模型文心一言Python3.10接入

“文心”取自《文心雕龙》一书的开篇,作者刘勰在书中引述了一个古代典故:春秋时期,鲁国有一位名叫孔文子的大夫,他在学问上非常有造诣,但是他的儿子却不学无术,孔文子非常痛心。 一天,孔文子在…

字节跳动软件测试岗,前两面过了,第三面HR天坑!竟然跟我说……

阎王易见,小鬼难缠。我一直相信这个世界上好人居多,但是也没想到自己也会在阴沟里翻船。我感觉自己被字节跳动的HR坑了。在这里,我只想告诫大家,offer一定要拿到自己的手里才是真的,口头offer都是不牢靠的,…

算法刷题总结 (四) 动态规划

算法总结4 动态规划一、动态规划1.1、基础问题11.1.1、509. 斐波那契数列1.1.2、70. 爬楼梯1.1.3、746. 使用最小花费爬楼梯1.2、基础问题21.2.1、62. 不同路径1.2.2、63. 不同路径Ⅱ1.2.3、64. 最小路径和1.2.4、343. 整数拆分1.2.5、96. 不同的二叉搜索树1.3、背包问题1.3.1、…

嵌入式学习笔记——STM32的时钟树

时钟树前言时钟树时钟分类时钟树框图LSI与LSEHSI、HSE与PLL系统时钟的产生举例AHB、APBx的时钟配置时钟树相关寄存器介绍1.时钟控制寄存器(RCC_CR)2.RCC PLL 配置寄存器 (RCC_PLLCFGR)3.RCC 时钟配置寄存器 (RCC_CFGR)4.RCC 时钟中断寄存器 (RCC_CIR)修改…

Java中的二叉树

文章目录前言一、树形结构(了解)1.1 概念1.2 概念(重要)1.3 树的表示形式(了解)1.4 树的应用二、二叉树(重点)2.1 概念2.2 两种特殊的二叉树2.3 二叉树的性质2.5 二叉树的存储2.5 二…

数据挖掘(2.2)--数据预处理

目录 二、数据描述 1.描述数据中心趋势 1.1平均值和截断均值 1.2加权平均值 1.3中位数(Median)和众数(Mode) 2.描述数据的分散程度 2.1箱线图 2.2方差和标准差 2.3正态分布 3.数据清洗 3.1数据缺失的处理 3.2数据清洗 二、数据描述 描述数…

【IDEA插件开发】环境搭建

基础信息 GRADLE 7.5.1 IDEA IntelliJ IDEA 2020.1.1 (Ultimate Edition) Build #IU-201.7223.91, built on April 30, 2020 Licensed to https://zhile.io You have a perpetual fallback license for this version Subscription is active until July 8, 2089 Runtime ve…

蓝桥杯嵌入式第一课--创建工程

概述学习本节之前,必须要先安装好 keil5 以及 CubeMX 等软硬件环境,如果你已经安装完成,请告诉自己:考试现在开始!从CubeMX开始CubeMX是创建工程模板的软件,也是我们比赛时第一个要进行操作的软件。一、选择…

【十二天学java】day01-Java基础语法

day01 - Java基础语法 1. 人机交互 1.1 什么是cmd? 就是在windows操作系统中,利用命令行的方式去操作计算机。 我们可以利用cmd命令去操作计算机,比如:打开文件,打开文件夹,创建文件夹等。 1.2 如何打…

介绍两款红队常用的信息收集组合工具

介绍两款红队常用的信息收集组合工具1.Ehole本地识别FOFA识别结果输出2.AlliN1.Ehole EHole(棱洞)3.0 红队重点攻击系统指纹探测工具 EHole是一款对资产中重点系统指纹识别的工具,在红队作战中,信息收集是必不可少的环节,如何才能从大量的资…

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(3)

写在前面: 怎么样才能学好一个算法? 我个人认为,系统性的刷题尤为重要, 所以,为了学好深度优先搜索,为了用好暴搜应对蓝桥杯, 事不宜迟,我们即刻开始刷题! 题目&…