JVM—运行时数据区
JVM—运行时数据区
运行时数据区概述
JVM运行时数据区如下图:

整个JVM构成里面,主要由三部分组成:类加载系统、运行时数据区、执行引擎。
按照线程使用情况和职责分成两大类:
- 线程独享(程序执行区域)
- 虚拟机栈、本地方法栈、程序计数器
- 不需要垃圾回收
- 线程共享(数据存储区域)
- 堆和方法区
- 存储类的静态数据和对象数据
- 需要垃圾回收
堆
Java堆在JVM启动时创建内存区域去实现对象、数组与运行时常量的内存分配,它是虚拟机管理最大的区域,也是垃圾回收的主要内存区域。

内存划分:
核心逻辑就是三大假说,基于程序运行情况进行不断地优化设计。
堆内存为什么会存在新生代和老年代?
分代收集理论:当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”(Generational Collection)的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:
- 弱分代假说 (Weak Generational Hypothesis) :绝大多数对象都是朝生夕灭的。
- 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。
- 如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;
- 如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域。
这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。
内存模型变迁
JDK 1.7内存模型如下图:

- Young 年轻区︰主要保存年轻对象,分为三部分,Eden区、两个Survivor区。
- Tenured年老区︰主要保存年长对象,当对象在Young复制转移一定的次数后,对象就会被转移到Tenured区。
- Perm永久区︰主要保存class、method、filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到OOM:PermGen space的错误。
- Virtual区:最大内存和初始内存的差值,就是Virtual区。
JDK 1.8内存模型如下图:

- 由2部分组成,新生代(Eden+ 2*Survivor ) +年老代(OldGen )
- JDK1.8中变化最大的是Perm永久区用Metaspace进行了替换
- 注意:区别于JDK1.7,Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中。
JDK 1.9内存模型如下图:

- 取消新生代、老年代的物理划分
- 将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的新生代、老年代区域
虚拟机栈
栈帧是什么
栈帧(Stack Frame)是用于支持虚拟机进行方法执行的数据结构。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。
栈内存为线程私有的空间,每个线程都会创建私有的栈内存,生命周期与线程相同,每个Java方法在执行的时候都会创建一个栈帧(Stack Frame)。栈内存大小决定了方法调用的深度,核内存过小则会导致方法调用的深度较小,如递归调用的次数较少。
虚拟机栈的构成如下:

当前栈帧
一个线程中方法的调用链可能会很长,所以会有很多栈帧。只有位于JVM虚拟机栈栈顶的元素才是有效的,即称为当前栈帧,与这个栈帧相关连的方法称为当前方法,定义这个方法的类叫做当前类。
执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了。
什么时候创建栈帧
调用新的方法时,新的栈帧也会随之创建。并且随着程序控制权转移到新方法,新的栈帧成为了当前栈帧。方法返回之际,原栈帧会返回方法的执行结果给之前的栈帧(返回给方法调用者),随后虚拟机将会丢弃此栈帧。
栈异常的两种情况
- 如果线程请求的栈深度大于虚拟机所允许的深度(Xss默认1m),会抛出StackOverflowError异常
- 如果在创建新的线程时,没有足够的内存去创建对应的虚拟机栈,会抛出OutOfMemoryError异常【不一定会复现,不同机器不同现象】
本地方法栈
本地方法栈和虚拟机栈相似,区别就是虚拟机栈为虚拟机执行Java服务(字节码服务),而本地方法栈为虚拟机使用到的Native方法(比如C++方法)服务。
简单地讲,一个Native Method就是一个Java调用非Java代码的接口。
方法区
方法区(Method Area)是可供各个线程共享的运行时内存区域,方法区本质上是Java语言编译后代码存储区域,它存储每一个类的结构信息,例如:运行时常量池、成员变量、方法数据、构造方法和普通方法的字节码指令等内容。很多语言都有类似区域。
方法区的具体实现有两种:永久代(PermGen)、元空间(Metaspace)。
方法区存储什么数据
方法区构成如下:

主要有如下三种类型:
- Class:
- 1.类型信息,比如Class
- 2.方法信息:比如Method(方法名称、方法参数列表、方法返回值信息)
- 3.字段信息:比如Field(字段类型、字段名称需要特殊设置才能保存的住)
- 4.类变量(静态变量):JDK1.7之后,转移到堆中存储
- 5.方法表(方法调用的时候):调用某个类的方法时,根据某个类的方法表去查找合适的方法进行调用的。
- 运行时常量池(字符串常量池):从class中的常量池加载而来,JDK1.7之后,转移到堆中存储
- 字面量类型
- 引用类型:内存地址
- JIT编译器编译之后的代码缓存
如果需要方法方法区中类的其他信息,都必须先获得Class对象,才能去方法该Class对象关联的方法信息或者字段信息。
永久代和元空间的区别是什么
- JDK1.8之前使用的方法区实现是永久代,JDK1.8及以后使用的实现是元空间;
- 存储位置不同:
- 永久代所使用的内存区域是JVM进程所使用的区域,它的大小受整个JVM的大小所限制;
- 元空间所使用的内存区域是物理内存区域,元空间的使用大小只会受物理内存大小的限制。
- 存储内容不同:
- 永久代存储的信息基本上就是上面方法区存储内容中的数据;
- 元空间只存储类的元信息,而静态变量和运行时常量池都挪到了堆中。
为什么要使用元空间来替代永久代
- 字符串存在永久代中,容易出现性能问题和永久代内存溢出;
- 类及方法的信息等比较难确定大小,所以对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出;
- 永久代会为GC带来不必要的复杂度,并且回收效率偏低。
方法区实现变迁历史如下图:

字符串常量池
三种常量池的比较
- class常量池:一个class文件只有一个class常量池
- 字面量:数值型(int、float、long、double)、双引号引起来的字符串值等
- 符号引用:Class、Method、Field等
- 运行时常量池:一个class对象有一个运行时常量池
- 字面量:数值型(int、float、long、double)、双引号引起来的字符串值等
- 符号引用:Class、Method、Field等
- 字符串常量池:全局只有一个字符串常量池·双引号引起来的字符串值
- 双引号引起来的字符串值
字符串的存储位置
单独使用""引号创建的字符串都是常量,编译期就已经确定存储到Sstring Pool中。
使用new String("")创建的对象会存储到heap中,是运行期新创建的。
使用只包含常量的字符串连接符如"aa"+"bb"创建的也是常量,编译期就能确定已经存储到StringPool中。
使用包含变量的字符串连接如"aa"+s创建的对象是运行期才创建的,存储到heap中。
运行期调用String的intern()方法可以向String Pool中动态添加对象。
程序计数器
程序计数器(Program Counter Register),也叫PC寄存器,是一块较小的内存空间,它可以看作是当前线程所执行的字节码指令的行号指示器。字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程回复等都需要依赖这个计数器来完成。
为什么需要程序计数器?
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(针对多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换(系统上下文切换)后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为"线程私有"的内存。
存储的什么数据?
如果一个线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器的值则为空。
异常:此内存区域是唯一一个在Java的虚拟机规范中没有规定任何OutOfMemoryError异常情况的区域。
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。在JDK1.4中新加入了NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
直接内存(堆外内存)与堆内存比较:
- 直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显;
- 直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显。
JVM—运行时数据区的更多相关文章
- Jvm运行时数据区
一:运行时数据区 Java虚拟机在执行Java程序的过程中会把它管理的内存分为若干个不同的数据区域.这些区域有着各自的用途,一级创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户 ...
- Java内存管理:Java内存区域 JVM运行时数据区
转自:https://blog.csdn.net/tjiyu/article/details/53915869 下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些, ...
- JVM 运行时数据区 (三)
JVM运行时数据区 运行时数据区由 程序计数器.java虚拟机栈.本地方法栈.堆.方法区 组成: 1.程序计数器 每一个Java线程都有一个程序计数器,用于保存程序执行到当前方法的哪一个指令,它是线程 ...
- JVM总结(一):概述--JVM运行时数据区
大三下,趁着寒假重温一遍JVM,准备在一个系列来总价一下学习JVM的整个过程.争取在接下来的一个星期内更新完这一个系列,然后回家过年. JVM运行时数据区 线程私有的数据区 程序计数器 虚拟机栈 本地 ...
- JVM运行时数据区与JVM堆内存模型小结
前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 ...
- JVM运行时数据区和垃圾回收机制
最近参考各种资料,尤其是<深入理解Java虚拟机 JVM高级特性和最佳实践>,大牛之作.把最近学习的Java虚拟机组成和垃圾回收机制总结一下. 你不会的都是新知识,学无止境,每天进步一点点 ...
- Jvm运行时数据区 —— Java虚拟机结构小记
关于jvm虚拟机的文章网上都讲烂了.尤其是jvm运行时数据区的内容. 抱着眼见为实的想法,自己翻了翻JVM规范,花了点时间稍微梳理了一下. 以下是阅读Java虚拟机规范(Java SE 8版)的第二章 ...
- Java中的字符串常量池和JVM运行时数据区的相关概念
什么是字符串常量池 JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池 工作原理 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量 ...
- JVM运行时数据区及对象在内存中初始化的过程
JVM运行时数据区 Java虚拟机所管理的内存区域,也称为运行时数据区,分为以下几个运行时数据区,如图所示 程序计数器:当前程序所执行字节码的行号指示器 程序计数器(Program Counter R ...
- JVM 运行时数据区(二)
@ 目录 运行时数据区 共享区 堆区 方法区 隔离区 虚拟机栈 栈帧 本地方法栈 程序计数器 运行时数据区 JVM 运行时数据区主要分为5块 方法区 JDK1.8以后叫做元数据区(Metaspace) ...
随机推荐
- 通过结巴分词 sklearn判断语句和例句集合最相近的句子
` import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pair ...
- OpenResty中如何实现,按QPS、时间范围、来源IP进行限流
OpenResty是一个基于Nginx与Lua的高性能Web平台,它通过LuaJIT在Nginx中运行高效的Lua脚本和模块,可以用来处理复杂的网络请求,并且支持各种流量控制和限制的功能. 近期研究在 ...
- socket及黏包现象及解决黏包---day28
1.四次挥手(补充) 客户端向服务端发送一个请求消息,断开连接(代表客户端没有数据传输了) 服务端接收请求,发出响应 等到服务端所有数据收发完毕之后 服务端向客户端发送断开连接的请求 客户端接收请求后 ...
- django学习第十二天--ajax请求和csrftoken认证的三种方式
基于cookie的登录认证装饰器 def check_login(f): def inner(request,*args,**kwargs): is_login = request.COOKIES.g ...
- 【Azure 应用服务】Azure Durable Function(持久函数)在执行Activity Function时候,因为调用函数名称错误而导致长时间无响应问题
问题描述 在使用Azure Durable Function函数,调用函数链模式来调用多个Activity Function. 函数链:https://docs.azure.cn/zh-cn/azur ...
- 文心一言 VS 讯飞星火 VS chatgpt (205)-- 算法导论15.4 1题
一.求〈1,0,0,1,0,1,0,1〉和〈0,1,0,1,1,0,1,1,0〉的一个LCS.需要写代码的时候,请用go语言. 文心一言,代码正常运行: 在Go语言中,求两个序列的最长公共子序列(Lo ...
- C 语言字符串操作总结
C 语言字符串操作总结 一.字符串操作 size_t 是一个无符号整型. 1.1 strcpy 函数原型:char *strcpy(char *dest, const char *src). 功 能: ...
- python AI应用开发编程实战 大模型实战基础(数据存储类型列表与字典)(二)
大模型开发中,需要和自己的业务融合,我们要对自己的数据处理,熟悉外理excle word pdf 数据然后处理后可以放到向量数据库,或者直接Assistants API传到大模型引用,不管怎么样数 ...
- vscode 尾逗号不自动删除 'comma-dangle': 'off' eslint vue
vscode 尾逗号不自动删除 'comma-dangle': 'off' eslint 外层环境说明 vscode eslint - .elintrs.js vue - vue开发 vetur - ...
- pandas DataFrame内存优化技巧:让数据处理更高效
Pandas无疑是我们数据分析时一个不可或缺的工具,它以其强大的数据处理能力.灵活的数据结构以及易于上手的API赢得了广大数据分析师和机器学习工程师的喜爱. 然而,随着数据量的不断增长,如何高效.合理 ...