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虚拟机》 - 第三版
相关文章:

JVM学习.01 内存模型
1、前言对于C、C程序员来说,在内存管理领域,他们拥有对象的“所有权”。从对象建立到内存分配,不仅需要照顾到对象的生,还得照顾到对象的消亡。背负着每个对象生命开始到结束的维护和管理责任。对于JAVA程序来说,因为J…...

R+VIC模型应用及未来气候变化模型预测
RVIC模型融合实践技术应用及未来气候变化模型预测在气候变化问题日益严重的今天,水文模型在防洪规划,未来预测等方面发挥着不可替代的重要作用。目前,无论是工程实践或是科学研究中都存在很多著名的水文模型如SWAT/HSPF/HEC-HMS等。虽然&…...

搞懂vue 的 render 函数, 并使用
render函数是什么 简单的说,在vue中我们使用模板HTML语法组建页面的,使用render函数我们可以用js语言来构建DOM 因为vue是虚拟DOM,所以在拿到template模板时也要转译成VNode(虚拟节点)的函数,而用render函数构建DOM,vu…...

【Linux】GDB的安装与使用
安装安装gdb的具体步骤如下:1、查看当前gdb安装情况rpm -qa | grep gdb如果有,则可以先删除:rpm -e --nodeps 文件名如果没有,则进行下一步。2、下载gdb源码包或者直接apt安装。apt命令安装:sudo apt install gdb源码包…...

MySQL索引特性
文章目录为什么要有索引?认识磁盘磁盘的结构磁盘的盘片结构定位扇区磁盘随机访问 (Random Access)与连续访问 (Sequential Access)MySQL与磁盘交互索引的理解测试主键索引索引的原理索引结构是否可以使用其他数据结构B树 vs B树聚簇索引 vs 非聚簇索引为什么要有索引…...

Python 面向对象编程——类定义与对象
<类定义与对象声明> 面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥…...

基于 Apache Flink 的实时计算数据流业务引擎在京东零售的实践和落地
摘要:本文整理自京东零售-技术研发与数据中心张颖&闫莉刚在 ApacheCon Asia 2022 的分享。内容主要包括五个方面: 京东零售实时计算的现状实时计算框架场景优化:TopN场景优化:动线分析场景优化:FLINK 一站式机器学…...

【JavaEE】如何将JavaWeb项目部署到Linux云服务器?
写在前面 大家好,我是黄小黄。不久前,我们基于 servlet 和 jdbc 完善了博客系统。本文将以该系统为例,演示如何将博客系统部署到 Linux 云服务器。 博客系统传送门: 【JavaEE】前后端分离实现博客系统(页面构建&#…...

Mysql常用命令
mysql连接: [roothost]# mysql -u root -p Enter password:******创建数据库: CREATE DATABASE 数据库名; 删除数据库: drop database 数据库名; 使用mysqladmin删除数据库: [roothost]# mysqladmin -u root -p dr…...

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(4)
目录 写在前面: 题目:P1149 [NOIP2008 提高组] 火柴棒等式 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述: 输入格式: 输出格式: 输入样例: 输出样例: 解题思路: …...

在Win10以及SDK为33的环境下——小米便签项目的搭建
文章目录0. 我的操作系统和开发环境1. 相关文件下载:2. import project:2.1 用import project导入项目3. make project:3.1 AS中的命令行乱码问题:3.2 依赖库缺失问题:3.3 关于targetSdkVersion3.4 关于Missing URL3.5 关于Manifest merger f…...

FPGA纯verilog实现RIFFA的PCIE通信,提供工程源码和软件驱动
目录1、前言2、RIFFA简介RIFFA概述RIFFA架构RIFFA驱动3、vivado工程详解4、上板调试验证并演示5、福利:工程代码的获取1、前言 PCIE是目前速率很高的外部板卡与CPU通信的方案之一,广泛应用于电脑主板与外部板卡的通讯,PCIE协议极其复杂&…...

Linux网络配置
文章目录一、Linux网络配置原理图二、查看网络IP和网关ping测试主机之间网络连通性三、linux网络环境配置第一种方法(自动获取)第二种方法(指定ip)四、设置主机名和hosts映射设置主机名设置hosts映射五、主机名解析过程分析(Hosts、DNS)Hosts是什么DNS一、Linux网络配置原理图 …...

【Java学习笔记】多线程与线程池
多线程与线程池一、多线程安全与应用1、程序、进程与线程的关系2、创建多线程的三种方式(1)继承Thread类创建线程【不推荐】(2)实现Runnable接口创建线程(3)Callable接口创建线程3、线程的生命周期4、初识线…...

尺取法
尺取法是一种线性的高效率算法。记 (L, R ) 为一个序列内以L为起点的最短合法区间, 如果R随L的增大而增大的,就可以使用尺取法。具体的做法是不断的枚举 L,同时求出R。 因为 R 随 L增大而增大,所以总时间复杂度为 O(n) 指针i、j的两种方向: 反向扫描:i、j方向相反,i从头…...

20.有效的括号
给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括…...

使用QT C++编写一个带有菜单和工具条的文本编辑器
您好,这是必应。我可以帮您生成一段使用QT C编写一个带有菜单和工具条的文本编辑器的代码,但是请注意,这只是我的创造性的输出,并不代表任何权威或专业的观点。如果您想要了解更多的相关知识,请自行搜索或咨询专家。以…...

文法和语言的基本知识
一、什么形式化的方法用一套带有严格规定的符号体系来描述问题的方法二、什么是非形式化的方法对程序设计语言的描述从语法、语义和语用三个方面因素来考虑所谓语法是对语言结构定义所谓语义是描述了语言的含义所谓语用则是从使用的角度去描述语言三、符号串字母表和符号串字母…...

学习其他人的代码,成为更好的程序员
学习其他人的代码,成为更好的程序员1. 广泛阅读2. 分析代码3. 记笔记4. 实验5. 分享你的发现6. 结论参考如何成为一名更好的Python程序员??? 学习编码是一个持续的过程,需要实践、实验和向他人学习的意愿。提高编码技能的最佳方法之一是学习他人的代…...

新星计划-JAVA学习路线及书籍推荐
CSDN的各位友友们你们好,今天千泽为大家带来的是JAVA学习路线及其经典书籍推荐,接下来让我们一起了解一下JAVA的学习路线吧!如果对您有帮助的话希望能够得到您的支持和关注,我会持续更新的! 目录 1.JAVASE及其书籍推荐 2.初级数据结构与算法及其书籍推荐 3.MySQL及其书籍推荐…...

【大数据】Hive系列之- Hive-DML 数据操作
Hive系列-DML 数据操作数据导入向表中装载数据(Load)语法操作用例通过查询语句向表中插入数据(Insert)创建一张表插入数据基本模式插入(根据单张表查询结果)查询语句中创建表并加载数据(As Sele…...

day2 —— 判断字符串中的字符是否唯一
目录 前言 问题描述 代码解释 前言 若是想要了解基本语法的话,请到(7条消息) C语言从练气期到渡劫期_要一杯卡布奇诺的博客-CSDN博客查看相应的语法细节 强烈安利这篇文章 —— (4条消息) 筑基五层 —— 位运算看这篇就行了_要一杯卡布奇诺的博客-CSDN博客 问题…...

176万,GPT-4发布了,如何查看OpenAI的下载量?
大家好,这里是程序员晚枫。 昨天新一代GPT4发布了,今年GPT不断给大家带来惊喜。 在OpenAI的官网,也公开了GPT的Python调用第三方库:openai。 今天我们就来看看,这个Python智能接口~ 1、代码说明 开发过Python项目…...

蓝蓝算法题(一)
讲在前面:1.本人正在逐步学习C,代码中难免有C和C(向下兼容)混用情况。2.算法题目来自蓝蓝知识星球,没有对应的判决系统,运行到判决系统可以会有部分案例不能通过。 求素数 暴力求解(1 - n试探…...

Python截图自动化工具
1、展示部分源码(写的比较乱,哈哈) 2、功能展示 1)首页 2)按钮截图(用于自动翻页) 3)保存位置按钮(选择图片保存的位置) 4)重复次数,就是要截取多少次 5)定位截屏(截取的内容&#x…...

网络作业2【计算机网络】
网络作业2【计算机网络】前言推荐网络作业2一. 单选题(共3题,19.8分)二. 多选题(共1题,6.6分)三. 填空题(共8题,52.8分)四. 判断题(共3题,20.8分&…...

如何给网页加速,如何加速网页速度?
如何加速网页速度?提高移动网页加载的速度,可以从服务器的优化、网页的容量、请求响应等方面入手,这些方面优化后必然可以提高加载速度。1、服务器硬件软件配置要好,网络、读写响应等要做好优化。2、可以开启gzip压缩技术…...

linux kernel 5.0 inline hook框架
github:https://github.com/WeiJiLab/kernel-hook-framework 一、项目介绍 Usually we want to hack a kernel function, to insert customized code before or after a certain kernel function been called, or to totally replace a function with new one. How can we…...

【Java版oj】day12二进制插入、查找组成一个偶数最接近的两个素数
目录 一、二进制插入 (1)原题再现 (2)问题分析 (3)完整代码 二、查找组成一个偶数最接近的两个素数 (1)原题再现 (2)问题分析 (3࿰…...

【24】Verilog进阶 - 序列检测2
VL35 状态机-非重叠的序列检测 1 思路 状态机嘛,也是比较熟悉的朋友啦, 我就火速写出了STG。如下黑色所示: 2 初版代码 `timescale 1ns/1nsmodule sequence_test1(input wire clk ,input wire rst ,input wire data ,output reg flag ); //*************code**********…...