论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
ToB门户
了解全球最新的ToB事件
博客
Blog
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
ToB企服应用市场:ToB评测及商务社交产业平台
»
论坛
›
软件与程序人生
›
后端开发
›
Java
›
Java内存地区与内存溢出异常 - 运行时数据区 ...
Java内存地区与内存溢出异常 - 运行时数据区
魏晓东
金牌会员
|
2024-7-31 21:40:32
|
显示全部楼层
|
阅读模式
楼主
主题
829
|
帖子
829
|
积分
2487
一、运行时数据区
1.1 步调计数器 - 线程私有
可以看做当前线程所执行的字节码行号指示器,在任意时候一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。所以为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的线程计数器,各条线程之间计数器互不影响,独立存储。
线程执行方法:
Java 方法 - 线程计数器记录虚拟机字节码指令的地点
Native 方法 - 计数器为空(Undefined)
此地区是唯一一个不会产生 OutOfMemoryError 环境的地区
1.2 Java 虚拟机栈 - 线程私有
每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态毗连、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
1.2.1 局部变量表
局部变量表存放了编译期可知的各种 Java 虚拟机根本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,大概是一个指向对象起始地点的引用指针,也大概是指向一个代表对象的句柄或者其他与此对象干系的位置)和 returnAddress 类型(指向了一条字节码指令的地点)。
这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,此中 64 位长度的 long 和 double 类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的巨细。请读者留意,这里说的“巨细”是指变量槽的数目,虚拟机真正使用多大的内存空间(譬如按照1个变量槽占用32个比特、64 个比特,或者更多)来实现一个变量槽,这是完全由具体的虚拟机实现自行决定的事情。
上面的描述大概比较难记忆和明白,我们可以通过 JDK 提供的
javap
下令来反汇编一个 class 文件看看。
先编写一个类,Main 方法中定义根本类型和应用类型:
public class Main {
public static void main(String[] args) {
boolean isBooleanType = true;
byte byteType = 24;
char charType = 's';
short shortType = 16;
float floatType = 32;
long longType = 648;
double doubleType = 64;
int intType = 11;
People people = new People("张三", 18);
System.out.println("Hello world!");
}
}
复制代码
通过 java 运行得到 .class 字节码文件,然后通过 javap -l Main.class 下令反汇编查看行和局部变量表:
Compiled from "Main.java"
public class com.snails.Main {
public com.snails.Main();
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/snails/Main;
public static void main(java.lang.String[]);
LineNumberTable:
line 7: 0
line 8: 2
line 9: 5
line 10: 8
line 11: 12
line 12: 16
line 13: 21
line 14: 26
line 15: 30
line 16: 43
line 17: 51
LocalVariableTable:
Start Length Slot Name Signature
0 52 0 args [Ljava/lang/String;
2 50 1 isBooleanType Z
5 47 2 byteType B
8 44 3 charType C
12 40 4 shortType S
16 36 5 floatType F
21 31 6 longType J
26 26 8 doubleType D
30 22 10 intType I
43 9 11 people Lcom/snails/entity/People;
}
复制代码
可以看到 longType 的 Slot 索引位置是6 到下一个 doubleType 的索引位置 8 确占用 2 个变量槽,doubleType 同理。验证了上面所说的变量槽 Slot 占用巨细。
异常环境抛出如下:
if (线程请求的栈深度 > 虚拟机所允许的深度) {
throw new StackOverflowError();
} else {
throw new OutOfMemoryError();
}
复制代码
1.3 当地方法栈
当地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而当地方法栈则是为虚拟机使用到的当地(Native)方法服务。
《Java虚拟机规范》对当地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的 Java 虚拟机(譬如Hot-Spot虚拟机)直接就把当地方法栈和虚拟机栈合二为一。与虚拟机栈一样,当地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出 StackOverflowError 和 OutOfMemoryError 异常。
1.4 Java 堆
Java 堆是被所有线程共享的一块内存地区,在虚拟机启动时创建。Java世界里“几乎”所有的对象实例都在这里分配内存。
Java堆是垃圾网络器管理的内存地区,因此一些资料中它也被称作“GC堆”。从接纳内存的角度看,由于当代垃圾网络器大部分都是基于分代网络理论设计的,所以Java堆中经常会出现“新生代”“老年代”“永世代”“Eden空间”“From Survivor空间”“To Survivor空间”等名词。在十年之前(以G1网络器的出现为分界),作为业界绝对主流的HotSpot虚拟机,它内部的垃圾网络器全部都基于“经典分代”[3]来设计,需要新生代、老年代网络器搭配才能工作,在这种背景下,上述说法还算是不会产生太大歧义。但是到了今天,垃圾网络器技术与十年前已不可同日而语,HotSpot里面也出现了不接纳分代设计的新垃圾网络器,再按照上面的提法就有很多需要商榷的地方了。
根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的思量,很大概会要求连续的内存空间。
Java堆既可以被实现成固定巨细的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数 -Xmx 和 -Xms 设定)。假如在 Java 堆中没有内存完成实例分配,而且堆也无法再扩展时,Java虚拟机将会抛出 OutOfMemoryError 异常。
1.5 方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存地区,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
JDK 8以前,很多Java步调员都习惯在HotSpot虚拟机上开辟、部署步调,很多人都更乐意把方法区称呼为“永世代”(PermanentGeneration),或将两者混为一谈。
到了JDK 7的HotSpot,已经把原本放在永世代的字符串常量池、静态变量等移出,而到了JDK 8,终于完全废弃了永世代的概念,改用与JRockit、J9一样在当地内存中实现的元空间(Meta-space)来取代,把JDK 7中永世代还剩余的内容(主要是类型信息)全部移到元空间中。
这地区的内存接纳目的主要是针对常量池的接纳和对类型的卸载,一样平常来说这个地区的接纳效果比较难令人满足,尤其是类型的卸载,条件相当苛刻,但是这部分地区的接纳有时又确实是须要的。
根据《Java虚拟机规范》的规定,假如方法区无法满足新的内存分配需求时,将抛出 OutOfMemoryError 异常。
1.6 运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,另有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
运行时常量池相对于Class文件常量池的别的一个重要特征是具备动态性,Java语言并不要求常量肯定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开辟人员使用得比较多的便是String类的intern()方法。
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
1.7 直接内存
在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著进步性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
显然,本机直接内存的分配不会受到 Java 堆巨细的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)巨细以及处理器寻址空间的限制,一样平常服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存地区总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError 异常。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
本帖子中包含更多资源
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
回复
使用道具
举报
0 个回复
倒序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
回帖后跳转到最后一页
发新帖
回复
魏晓东
金牌会员
这个人很懒什么都没写!
楼主热帖
基础常用dos命令
Unity技术手册 - Shader实现灵魂状态 ...
云原生之旅 - 14)遵循 GitOps 实践的 ...
.NET主流的几款重量级 ORM框架 ...
Vulnhub靶机-Al-Web-1
火山引擎 DataLeap 计算治理自动化解决 ...
vuluhub_jangow-01-1.0.1
Java集合框架(三)-HashSet
Android studio连接MySQL并完成简单的 ...
弱隔离级别 & 事务并发问题 ...
标签云
存储
挺好的
服务器
快速回复
返回顶部
返回列表