JVM运行时数据区之堆空间
JVM运行时数据区之堆空间
1.核心概述
一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。堆区在JVM 启动的时候即被创建,其空间大小也就确定了,是JVM管理的最大一块内存空间。
《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area fromwhich memory for all class instances and arrays is allocated)我要说的是:“几乎”所有的对象实例都在这里分配内存。一从实际使用角度看的。
- 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。
- 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
- 堆,是GC ( Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。
2.内部结构
现代垃圾收集器大部分都基于分代收集理论设计,堆空间的内部结构细分为:
Java 7及之前堆内存逻辑上分为三部分:新生区+养老区+永久区
- YoungGeneration Space 新生区 Young/New
- 又被划分为Eden区和Survivor区
- Tenure generation space 养老区 Old/Tenure
- Permanent Space 永久区 Perm
Java 8及之后堆内存逻辑上分为三部分: 新生区+养老区+元空间
- YoungGeneration Space 新生区 Young/New
- 又被划分为Eden区和Survivor区
- Tenure generation space 养老区 Old/Tenure
- Meta Space 元空间 Meta
JDK7的内部结构图
变化
3.年轻代与老年代
存储在JVM中的Java对象可以被划分为两类:
一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
另外一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致。
Java堆区进一步细分的话,可以划分为其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区、to区)。
3.1配置
下面这参数开发中一般不会调:
配置新生代与老年代在堆结构的占比
默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5
在HotSpot中,Eden空间和另外两个Survivor空间缺省所占的比例是8:1:1,当然开发人员可以通过选项 -XX:SurvivorRatio”调整这个空间比例。比如-XX:SurvivorRatio=8
几乎所有的Java对象都是在Eden区被new出来的。绝大部分的Java对象的销毁都在新生代进行了。IBM公司的专门研究表明,新生代中 80%的对象都是“朝生夕死”的。
4.对象分配过程
- 为新对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否会在内存空间中产生内存碎片。
- new的对象先放伊甸园区。此区有大小限制。
- 当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区
- 然后将伊甸园中的剩余对象移动到幸存者0区
- 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会放到幸存者1区。
- 如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区
- 什么时候能去养老区呢?可以设置次数。默认是15次。可以设置参数:-XX:MaxTenuringThreshold=N进行设置
- 在养老区,相对悠闲。当养老区内存不足时,再次触发GC: Major GC,进行养老区的内存清理。
- 若养老区执行了Major GC之后发现依然无法进行对象的保存,就会产生OOM异常
java.lang.OutOfMemoryError: Java heap space
5.内存分配策略
5.1堆空间分代思想
- 经研究,不同对象的生命周期不同。70%-99%的对象是临时对象
- 新生代:有Eden、两块大小相同的Survivor(又称为from/to,s0/s1)构成to总为空。
- 老年代:存放新生代中经历多次GC仍然存活的对象。
通过将堆分代,可以将生命周期较短的对象放在年轻代中,而将生命周期较长的对象放在老年代中。这样,垃圾回收器在回收时,可以针对不同代的对象采用不同的策略。对于年轻代中的对象,可以快速扫描和回收,而对于老年代中的对象,则需要经过多次垃圾回收后才能被回收。这种分代思想可以优化垃圾回收的效率,提高程序性能。
如果不分代,所有对象都放在同一代中,会导致垃圾回收效率变低,因为垃圾回收器需要扫描整个堆来查找需要回收的对象。同时,不分代也会导致内存浪费,因为一些对象虽然已经被释放,但是它们的内存空间并没有被回收。因此,分代是Java垃圾回收的重要优化策略,可以提高程序性能和可靠性。
5.2分配原则
优先分配到Eden
大对象直接分配到老年代,尽量避免程序中出现过多的大对象
长期存活的对象分配到老年代
动态对象年龄判断如果survivor区中相同年龄的所有对象大小的总和大于survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreshold 中要求的年龄。
6.TLAB
TLAB(Thread Local Allocation Buffer)是Java虚拟机的一种内存分配优化技术。它为每个线程分配一块私有的内存区域,称为TLAB(Thread Local Allocation Buffer),使得每个线程都拥有自己的内存空间,从而避免多线程之间的内存竞争和同步问题,提高了内存分配的效率。因为堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据,对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的
TLAB的大小是固定的,当TLAB用满时,会新申请一个TLAB,而老TLAB里的对象还留在原地,无法感知自己是否是从TLAB分配出来的。当分配一次以后内存还是不够时,会直接移入Eden区。
TLAB的优点是提高了内存分配的效率,减少了多线程之间的竞争和同步问题。但是,由于TLAB的大小是固定的,可能会出现浪费空间的情况,导致Eden区空间不连续,积少成多。因此,在创建大量对象时,应该考虑调整堆结构或使用对象池等技术来避免TLAB的缺点。
尽管不是所有的对象实例都能够在TLAB中成功分配内存,但JVM确实是将TLAB作为内存分配的首选。
在程序中,开发人员可以通过选项“-XX:UseTLAB”设置是否开启TLAB空间。
默认情况下,TLAB空间的内存非常小,仅占有整个Eden空间的1%,当然我们可以通过选项“-XX:TLABWasteTargetPercent”设置TLAB空间所占用Eden空间的百分比大小。
一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存。
7.堆空间常用的参数设置
oracle官网配置:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-XX:+PrintFlagsInitial : 查看所有的参数的默认初始值
-XX:+PrintFlaqsFinal :查看所有的参数的最终值 (可能会存在修改-XX:+PrintFlaqsFinal不再是初始值)
-Xms:初始堆空间内存 (默认为物理内存的1/64)
-Xmx:最大堆空间内存(默认为物理内存的1/4)
-Xmn:设置新生代的大小。(初始值及最大值)
-XX:NewRatio:配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio:设置新生代中Eden和s0/S1空间的比例
-XX:+PrintGCDetails:输出详细的GC处理日志
打印gc简要信息
-XX:+PrintGC
-verbose:gc
-XX:HandlePromotionFailure: 是否设置空间分配担保
-XX:MaxTenuringThreshold: 设置新生代垃圾的最大年龄
8.简单讲述几种GC
JVM在进行GC时,并非每次都对上面三个内存(新生代、老年代;方法区)区域一起回收的,大部分时候回收的都是指新生代。
针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:
部分收集(Partial GC):不是完整收集整个Java堆的垃圾收集。其中又分为:
- 新生代收集 (Minor GC / Young Gc):只是新生代的垃圾收集
- 老年代收集(Major Gc / old Gc):只是老年代的垃圾收集
- 混合收集 (Mixed GC): 收集整个新生代以及部分老年代的垃圾收集。目前,只有G1 GC会有这种行为
一种是整堆收集 (Full GC):收集整个java堆和方法区的垃圾收集
8.1Minor GC(年轻代GC)
当年轻代空间不足时,就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC(每次 Minor GC 会清理年轻代的内存)
因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线Minor程才恢复运行。
8.2Major GC(老年代GC)
出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在ParallelScavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。
也就是在老年代空间不足时,会先尝试触发Minor GC。如果之后空间还不足则触发Major GC
Major GC的速度一般会比Minor G慢10倍以上,STW的时间更长,如果Major GC后,内存还不足,就报OOM了。
8.3Full GC
触发FullGC 执行的情况有如下五种:
(1)调用System.gc()时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
3)方法区空间不足
(4)通过minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、survivor space0 (From Space) 区向survivor space1(ToSpace) 区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
说明: full gc是开发或调优中尽量要避免的。这样暂时时间会短一些
9.小结
年轻代是对象的诞生、成长、消亡的区域,一个对象在这里产生、应用,最后被垃圾回收器收集、结束生命。
老年代放置长生命周期的对象,通常都是从survivor区域筛选拷贝过来的Java对象。当然,也有特殊情况,我们知道普通的对象会被分配在TLAB上;如果对象较大,JVM会试图直接分配在Eden其他位置上;如果对象太大,完全无法在新生代找到足够长的连续空闲空间,JVM就会直接分配到老年代
当GC只发生在年轻代中,回收年轻代对象的行为被称为MinorGC。当GC发生在老年代时则被称为MajorGC 或者FulIGC。一般的,MinorGC 的发生频率要比MajorGc高很多,即老年代中垃圾回收发生的频率将大大低于年轻代。
其实堆空间并不是对象分配的唯一选择,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙变化,下一节写关于逃逸分析的一些技术
JVM运行时数据区之堆空间的更多相关文章
- JVM 运行时数据区(二)
@ 目录 运行时数据区 共享区 堆区 方法区 隔离区 虚拟机栈 栈帧 本地方法栈 程序计数器 运行时数据区 JVM 运行时数据区主要分为5块 方法区 JDK1.8以后叫做元数据区(Metaspace) ...
- JVM运行时数据区与JVM堆内存模型小结
前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那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运行时数据区和垃圾回收机制
最近参考各种资料,尤其是<深入理解Java虚拟机 JVM高级特性和最佳实践>,大牛之作.把最近学习的Java虚拟机组成和垃圾回收机制总结一下. 你不会的都是新知识,学无止境,每天进步一点点 ...
- JVM运行时数据区及对象在内存中初始化的过程
JVM运行时数据区 Java虚拟机所管理的内存区域,也称为运行时数据区,分为以下几个运行时数据区,如图所示 程序计数器:当前程序所执行字节码的行号指示器 程序计数器(Program Counter R ...
- Java中的字符串常量池和JVM运行时数据区的相关概念
什么是字符串常量池 JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池 工作原理 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量 ...
- Jvm运行时数据区 —— Java虚拟机结构小记
关于jvm虚拟机的文章网上都讲烂了.尤其是jvm运行时数据区的内容. 抱着眼见为实的想法,自己翻了翻JVM规范,花了点时间稍微梳理了一下. 以下是阅读Java虚拟机规范(Java SE 8版)的第二章 ...
随机推荐
- APISIX Ingress 如何使用 Cert Manager 管理证书
Apache APISIX Ingress Controller 是一款以 Apache APISIX 作为数据面的 Kubernetes Ingress Controller 开源工具,目前已经更新 ...
- LLM(大语言模型)解码时是怎么生成文本的?
Part1配置及参数 transformers==4.28.1 源码地址:transformers/configuration_utils.py at v4.28.1 · huggingface/tr ...
- 学习笔记——树形dp
树形 dp 介绍 概念 树形 dp,顾名思义,就是在树上做 dp,将 dp 的思想建立在树状结构之上. 常见的树形 dp 有两种转移方向: 从叶节点向根节点转移,这种也是树形 dp 中较为常见的一种. ...
- 深度学习04-(Tensorflow简介、图与会话、张量基本操作、Tensorboard可视化、综合案例:线性回归)
深度学习04-Tensorflow 深度学习04-(Tensorflow) Tensorflow概述 Tensorflow简介 什么是Tensorflow Tensorflow的特点 Tensorfl ...
- 基于YOLOv5的目标检测系统详解(附MATLAB GUI版代码)
摘要:本文重点介绍了基于YOLOv5目标检测系统的MATLAB实现,用于智能检测物体种类并记录和保存结果,对各种物体检测结果可视化,提高目标识别的便捷性和准确性.本文详细阐述了目标检测系统的原理,并给 ...
- Pwn系列之Protostar靶场 Stack2题解
(gdb) disass main Dump of assembler code for function main: 0x08048494 <main+0>: push ebp 0x08 ...
- git仓库过渡,同时向两个仓库推送代码
公司部门被大佬收购,产品项目迁移新公司仓库,过渡期间产品上线流程继续使用原公司的,新公司部署新系统后通过域名重定向逐渐将用户引流到新系统上完成切换,最后关闭原公司系统及上线流程. 过渡期间新功能代码需 ...
- 2022-04-22:给你两个正整数数组 nums 和 target ,两个数组长度相等。 在一次操作中,你可以选择两个 不同 的下标 i 和 j , 其中 0 <= i, j < nums.leng
2022-04-22:给你两个正整数数组 nums 和 target ,两个数组长度相等. 在一次操作中,你可以选择两个 不同 的下标 i 和 j , 其中 0 <= i, j < num ...
- 深入了解Js中的对象
在JavaScript中,对象是个无序的键值对数据集.例如: var xiaoqiang={ name:"wangqiang", age:30, city:"guangz ...
- 痞子衡嵌入式:MCUBootUtility v5.0发布,初步支持i.MXRT1180
-- 痞子衡维护的NXP-MCUBootUtility工具距离上一个大版本(v4.0.0)发布过去4个多月了,期间痞子衡也做过两个小版本更新,但不足以单独介绍.这一次痞子衡为大家带来了全新大版本v5. ...