JVM--堆

article/2025/11/7 15:13:40

45. JVM--堆

1. 堆的核心概述

一个进程对应一个JVM的实例,一个JVM实例中只有一个运行时数据区,里面只有一个方法区和堆,一个进程的多个线程共享方法区和堆,那就要考虑线程的安全问题。
每个线程各有一套程序计数器、本地方法栈、虚拟机栈。
1、一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
2、Java堆区在JVM启动的时候即被创建,其空间大小也就确定了,堆是JVM管理的最大一块内存空间, 并且堆内存的大小是可以调节的。
3、《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
4、所有的线程共享Java堆,并发性就差,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB),提高并发性。
5、《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is allocated)
6、“几乎”所有的对象实例都在这里分配内存。因为还有一些对象是在栈上分配的
7、数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。
8、在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。也就是触发了GC的时候,才会进行回收。
如果栈里面对象引用一出栈,堆里面的对象立马GC,会造成GC的频率特别高,会影响用户线程的执行
9、堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。
虚拟机栈没有GC,只有入栈和出栈
对象实体所属的类是什么,定义的结构方法都在哪里?在方法区中。
一个JVM实例只存在一个堆内存,并且堆内存的大小是可以调节的
如何设置堆内存大小
-Xms10m -Xmx10m
Xms:堆区的起始内存
Xmx:堆区的最大内存
使用 JDK 自带的工具:Java VisualVM ,来查看堆内存
Java VisualVM 在 JDK 的 bin 目录下
堆空间设置的大小=新生代+老年代,不包括元空间。

● 堆内存分区:


2. 设置堆内存大小与 OOM

● 设置堆空间大小

1、Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,大家可以通过选项"-Xms"和"-Xmx"来进行设置。
-Xms用于表示堆区的起始内存,等价于-XX:InitialHeapSize
-Xmx则用于表示堆区的最大内存,等价于-XX:MaxHeapSize
2、一旦堆区中的内存大小超过“-Xmx"所指定的最大内存时,将会抛出OutofMemoryError异常。
3、通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在Java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能。
4、默认情况下:
初始内存大小:物理电脑内存大小/64
最大内存大小:物理电脑内存大小/4
/**
 * 1. 设置堆空间大小的参数
 * -Xms 用来设置堆空间(年轻代+老年代)的初始内存大小
 *      -X 是jvm的运行参数
 *      ms 是memory start
 * -Xmx 用来设置堆空间(年轻代+老年代)的最大内存大小
 *
 * 2. 默认堆空间的大小
 *    初始内存大小:物理电脑内存大小 / 64
*    最大内存大小:物理电脑内存大小 / 4
 * 3. 手动设置:-Xms600m -Xmx600m
 *     开发中建议将初始堆内存和最大的堆内存设置成相同的值。
        因为如果不一样,空间不够要扩容,空闲时候要释放,频繁的扩容和释放会造成系统的压力
  */ 
public  class  HeapSpaceInitial {
public  static  void  main(String[] args) {
//返回Java虚拟机中的堆内存总量
long  initialMemory = Runtime.getRuntime().totalMemory() /  1024  1024 ;
//返回Java虚拟机试图使用的最大堆内存量
long  maxMemory = Runtime.getRuntime().maxMemory() /  1024  1024 ;
System.out.println( "-Xms : "  + initialMemory +  "M" );
System.out.println( "-Xmx : "  + maxMemory +  "M" );
System.out.println( "系统内存大小为:"  + initialMemory *  64.0  1024  "G" );
System.out.println( "系统内存大小为:"  + maxMemory *  4.0  1024  "G" );
}
}
-Xms : 123M
-Xmx : 1801M
系统内存大小为:7.6875G
系统内存大小为:7.03515625G
● OOM 举例
/**
* -Xms600m -Xmx600m
*/
public  class  OOMTest {
public  static  void  main(String[] args) {
ArrayList<Picture> list =  new  ArrayList<>();
while ( true ){
try  {
Thread.sleep( 20 );
catch  (InterruptedException e) {
e.printStackTrace();
}
list.add( new  Picture( new  Random().nextInt( 1024  1024 )));
}
}
}
class  Picture{
private  byte [] pixels;
public  Picture( int  length) {
this .pixels =  new  byte [length];
}
}
监控堆内存变化:Old 区域一点一点在变大,直到最后一次垃圾回收器无法回收垃圾时,堆内存被撑爆,抛出 OutOfMemoryError 错误
堆内存变化图
分析原因:大对象导致堆内存溢出

3. 年轻代与老年代

● 存储在JVM中的Java对象可以被划分为两类:

1、一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
1)生命周期短的,及时回收即可
2)另外一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致
2、Java堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(oldGen)
3、其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区、to区,这两个区的大小是相等的)
对于生命周期比较短的,及时回收就可以了,对于生命周期比较长的,没必要每次GC的时候都判断要不要回收他,每次还不回收,就有点浪费, 那就可以放在不经常回收判断的位置就是老年代。
● 配置新生代与老年代的比例 (一般不会调)
1、默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
2、可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5
当发现在整个项目中,生命周期长的对象偏多,那么就可以通过调整老年代的大小,来进行调优
● 新生区中的比例
1、在HotSpot中,Eden空间和另外两个survivor空间缺省所占的比例是8 : 1 :1
当然开发人员可以通过选项-XX:SurvivorRatio调整这个空间比例。比如-XX:SurvivorRatio=8
2、几乎所有的Java对象都是在Eden区被new出来的。绝大部分的Java对象的销毁都在新生代进行了(有些大的对象在Eden区无法存储时候,将直接进入老年代)
3、IBM公司的专门研究表明,新生代中80%的对象都是“朝生夕死”的。
4、新生区的对象默认生命周期超过 15 ,就会去养老区养老

4. 图解对象分配过程

1、我们创建的对象,一般都是存放在Eden区的,当我们Eden区满了后,就会触发GC操作,一般被称为 YGC / Minor GC操作。
Eden和S0、S1的比例是8:1:1,只有Eden满了才出发YGC,S0满了不会触发YGC,但是触发YGC时候会把Eden和S0一起回收。
2、当我们进行一次垃圾收集后, 红色的对象将会被回收(不再被使用,没有引用指向他),而绿色的还被使用着,存放在S0(Survivor From)区。
    同时我们给每个对象设置了一个年龄计数器, 经过一次回收后还存在的对象,将其年龄加 1。
3、同时Eden区继续存放对象,当Eden区再次存满的时候,又会触发一个MinorGC操作,此时GC将会把 Eden和Survivor From中的对象进行一次垃圾收集,
把存活的对象放到 Survivor To区,同时让存活的对象年龄 + 1, 现在S1是From区,S0是To区(经过一次MinorGC后谁空谁就是To区)。
4、我们继续不断的进行对象生成和垃圾回收,当Survivor中的对象的年龄达到15的时候,将会触发一次 Promotion 晋升的操作,也就是将年轻代中的对象晋升到老年代中.
可以设置新生区进入养老区的年龄限制,设置 JVM 参数:-XX:MaxTenuringThreshold=N 进行设置
在养老区,相对悠闲。当养老区内存不足时,再次触发GC:Major GC,进行养老区的内存清理
若养老区执行了Major GC之后,发现依然无法进行对象的保存,就会产生OOM异常。
总结:
1、针对幸存者s0, s1区的总结:复制之后有交换,谁空谁是to.
2、关于垃圾回收:频繁在新生区收集,很少在养老区收集,几乎不在永久区/元空间收集
-Xms:初始堆空间内存 (默认为物理内存的1/64)
-Xmx:最大堆空间内存(默认为物理内存的1/4)
-XX:NewRatio:配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
-XX:+PrintGCDetails:输出详细的GC处理日志

5. Minor GC、Major GC、Full GC

1、我们都知道,JVM的调优的一个环节,也就是垃圾收集,我们需要尽量的避免垃圾回收,因为在垃圾回收的过程中,容易出现STW(Stop the World)的问题,
     而 Major GC 和 Full GC出现STW的时间,是Minor GC的10倍以上
2、JVM在进行GC时,并非每次都对上面三个内存区域(新生代、老年代、方法区)一起回收的,大部分时候回收的都是指新生代。
针对Hotspot VM的实现, 它里面的GC按照回收区域又分为两大种类型: 一种是部分收集(Partial GC),一种是整堆收集(FullGC)
● 部分收集:
不是完整收集整个Java堆的垃圾收集。其中又分为:
1、新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
2、老年代收集:  (Major GC/Old GC):只是老年代的圾收集。
注意,很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。
● 整堆收集(Full GC)
收集整个java堆和方法区的垃圾收集。
● 年轻代 GC(Minor GC)触发机制
1、当年轻代空间不足时,就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。(每次Minor GC会清理年轻代的内存)
2、因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。
3、Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行,一般回收速度也比较快,所以对用户线程影响比较小,影响较大的是老年代GC,STW时间较长。
● 老年代 GC(MajorGC/Full GC)触发机制
1、指发生在老年代的GC,对象从老年代消失时,我们说 “Major Gc” 或 “Full GC” 发生了
2、出现了MajorGc,经常会伴随至少一次的Minor GC
但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行MajorGC的策略选择过程
也就是在老年代空间不足时,会先尝试触发Minor GC,如果之后空间还不足,则触发Major GC
3、Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长,如果Major GC后,内存还不足,就报OOM了

6. 堆空间分代思想:

● 为什么要把Java堆分代?不分代就不能正常工作了吗?
经研究,不同对象的生命周期不同。70%-99%的对象是临时对象。
1、新生代:有Eden、两块大小相同的survivor(又称为from/to,s0/s1)构成,to总为空。
2、老年代:存放新生代中经历多次GC仍然存活的对象。长期存活的对象分配到老年代
其实不分代完全可以, 分代的唯一理由就是优化GC性能
1、如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。GC的时候要找到哪些对象没用,这样就会对堆的所有区域进行扫描。
2、而很多对象都是朝生夕死的,如果分代的话,把新创建的对象放到某一地方,当GC的时候先把这块存储“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。
Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行,一般回收速度也比较快,所以对用户线程影响比较小,
 影响较大的是老年代GC,STW时间较长,对GC的优化减少GC出现的频率
就像疫情的核酸检测,将城阳区人群分为:1、本地没出去的人 2、疫区回来的人 3、得过新冠的人,如果都每隔两天进行全员核酸检测代价太大,对本地可以十天一次核酸检测,疫区回来的一周一次,得过的三天一次。

7. TLAB:Thread Local Allocation Buffer

●  问题:堆空间都是共享的么?
不一定,因为还有TLAB这个概念,在堆中划分出一块区域,为每个线程所独占
● 为什么有TLAB(Thread Local Allocation Buffer)?
1、TLAB:Thread Local Allocation Buffer,也就是为每个线程单独分配了一个缓冲区
2、堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
3、由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的,
为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。
● 什么是 TLAB?
1、从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分, JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。
2、多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。
1、尽管不是所有的对象实例都能够在TLAB中成功分配内存,但JVM确实是将TLAB作为内存分配的首选。
2、默认情况下,TLAB空间的内存非常小,仅占有整个Eden空间的1%
3、一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存。

● 堆小结:

1、老年代放置长生命周期的对象,通常都是从Survivor区域筛选拷贝过来的Java对象。
2、JVM为每个线程分配了一个私有缓存区域TLAB,将TLAB作为内存分配的首选
      如果对象较大,无法分配在 TLAB 上,则JVM会试图直接分配在Eden其他位置上;
     如果对象太大,完全无法在新生代找到足够长的连续空闲空间,JVM就会直接分配到老年代。
3、当GC只发生在年轻代中,回收年轻代对象的行为被称为Minor GC。 当GC发生在老年代时则被称为Major GC或者Full GC。
     一般的,Minor GC的发生频率要比Major GC高很多,即老年代中垃圾回收发生的频率将大大低于年轻代。

http://chatgpt.dhexx.cn/article/9QR3SPOs.shtml

相关文章

java中的堆

文章目录 一、堆的分类二、详述Java堆中各个区域1、堆大小2、新生代 ( Young ) 的划分3、工作原理4、GC日志 一、堆的分类 Java 中的堆是 JVM 管理的最大的一块内存空间&#xff0c;主要用于存放Java类的实例对象 其被划分为两个不同的区域&#xff1a;新生代 ( Young )和老年…

Java数据结构--堆

一.堆的定义及本质 要知道在Java中&#xff0c;堆是以优先级队列来表示的。因此学会了堆&#xff0c;我们也就学会优先级队列 1.堆的定义 当一颗二叉树的每个节点都大于等于它的两个子字节时&#xff0c;它被称为堆有序。而堆是一组能够用堆有序的完全二叉树排序的元素&…

Java数据结构之堆

堆的概念 堆逻辑上是一棵完全二叉树堆物理上是保存在数组中满足任意结点的值都大于其子树中结点的值&#xff0c;叫做大堆&#xff0c;或者大根堆&#xff0c;或者最大堆反之&#xff0c;则是小堆&#xff0c;或者小根堆&#xff0c;或者最小堆堆的基本作用是快速找集合中的最…

常用三极管引脚定义

给大家总结了常用封装的三极管的引脚定义 配套的视频讲解 常用三极管引脚定义

常用电子器件 ——三极管

说明&#xff1a;   本文章旨在总结备份、方便以后查询&#xff0c;由于是个人总结&#xff0c;如有不对&#xff0c;欢迎指正&#xff1b;另外&#xff0c;内容大部分来自网络、书籍、和各类手册&#xff0c;如若侵权请告知&#xff0c;马上删帖致歉。   QQ 群 号&#xf…

三极管开关特性

三极管在我们数字电路和模拟电路中都有大量的应用&#xff0c;在我们开发板上也用了多个三极管。在我们板子上的 LED 小灯部分&#xff0c;就有这个三极管的应用了&#xff0c;图 3-5 的 LED 电路中的 Q16就是一个 PNP 型的三极管。 图 3-5 LED 电路 三极管的初步认识 三极管…

常用三极管入门级电路设计(如何选型和搭电路?)

转自&#xff1a; 常用的三极管电路设计-电阻到底是怎么选的&#xff08;修正后&#xff09;-面包板社区 硬件基础知识---如何设计一个三极管放大电路_学无止境的专栏-CSDN博客_uce怎么算 今天的内容超级简单&#xff0c;主要给硬件新手写点东西&#xff0c;关于三极管实用方…

三极管相关知识点

1.两种类型的三极管图示如下&#xff0c;基极&#xff08;Base&#xff09;、集电极&#xff08;Collector&#xff09;和发射极&#xff08;Emitter&#xff09;&#xff0c;等效二极管电路如图所示 2.三极管的几种工作状态&#xff0c; 3.三极管做开关功能时&#xff0c;工…

三极管的一些基本知识

导通条件 NPN型三极管的导通条件是C点电位>B点电位>E点电位&#xff0c;三极管饱和导通的条件是Ub>Ue,Ub>Uc。 PNP型三极管的导通条件是E点电位>B点电位>C点电位&#xff0c;三极管饱和导通的条件是Ue>Ub,Uc>Ub。 NPN型三极管 1、定义&#xff1a; …

三极管的几点应用

关注星标公众号&#xff0c;不错过精彩内容 直接来源 | 巧学模电数电单片机 在复杂多变的模拟电路中&#xff0c;少不了学习三极管&#xff0c;过往的血泪史&#xff0c;不想再提&#xff0c;什么正偏反偏都给我统统滚蛋&#xff01;今天来点实际的。 三极管有三个工作状态&…

【三极管】

文章目录 一、图片&#xff08;NPN - PNP&#xff09;二、导通、截至、饱和条件&#xff08;一&#xff09;NPN&#xff08;二&#xff09;PNP 三、三极管的作用&#xff08;一&#xff09;开关作用 1、同或、异或、或非、与非—真值表 ① 同或&#xff1a;相同为1&#…

三极管常用封装的引脚排列

描述 三极管具有三个引脚&#xff0c;定义分别为基极b、集电极c、发射极e&#xff0c;在设计电路和设计封装时&#xff0c;这三个引脚的顺序必须和封装对应一致&#xff0c;否则电路无法正常工作&#xff0c;三极管常用的封装有&#xff1a;直插TO-92&#xff0c;贴片SOT-23、S…

电子元器件篇---三极管

目录 简介 三极管基本参数 2.1、封装形式 2.2、特征频率 2.3、工作电压/电流 2.4、电流放大倍数 2.5、集电极发射极反向击穿电压 2.6、最大允许耗散功率 三极管种类 三极管用途 4.1共射放大电路 4.2共集放大电路 4.3共基放大电路 4.4开关和反相电路 4.5稳压作用 简…

三极管的基础知识(上)

一、半导体三极管 1.作用 主要用于放大电路、开关电路。 2.三极管的结构与类型 &#xff08;1&#xff09;结构&#xff1a;三极管有两个PN结&#xff08;集电结、发射结&#xff09;、三个区&#xff08;集电区、基区、发射区&#xff09;、三个电极&#xff08;集电极、基…

三极管

三极管 一.三极管介绍参考资料 二. 三极管的作用1.开关来使用 2.二级驱动3.放大信号4.三极管还可以用来反向5.隔离参考来源 pnp型三极管电压总结 一.三极管介绍 三极管是最重要的电子元器件之一&#xff0c;成功制作世界上第一只半导体三极管的美国物理学家约翰巴丁(John Bard…

三极管与mos管通俗讲解

文章目录 一、三极管1.结构2.应用 二、mos管1.结构及原理2.mos管通断3.mos管最常用作用4.应用5.MOS管的栅极和源极之间的电阻 一、三极管 1.结构 电流控制型元器件&#xff0c;只要基极B有输入&#xff08;或输出&#xff09;电流就可以对这个晶体管进行控制了 三极管有三个…

三极管基础知识

二极管的导电特性 1. 正向偏置 在电子电路中&#xff0c;将二极管的正极接在高电位端&#xff0c;负极接在低电位端&#xff0c;二极管就会导通&#xff0c;这种连接方式&#xff0c;称为正向偏置。必须说明&#xff0c;当加在二极管两端的正向电压很小时&#xff0c;…

三极管基础分类, 参数选择及常见型号对比

三极管基础 具体的原理就不说了, N型和P型都是通过硅和锗参杂不同的元素带来的电子富余或缺少从而产生内在的电动势, 这里说的是如何帮助记忆. 半导体类型 P型半导体, p-type Semiconductor, p-type 这个名称代表的是 the positive charge of a hole, 知道这个就很好记忆了N…

常用三极管的区别 9012 9013 9014 9015 8550 8050

常用三极管的区别 9012 9013 9014 9015 8550 8050 转载自&#xff1a; http://hi.baidu.com/2000258051/blog/item/26f90b97f75ed66954fb961e.html 9013、9014均为NPN型三极管&#xff0c;最大耗散功率Pcm为0.625W&#xff1b;最大集电极电流Icm为0.5A&#xff1b;集电极-基极击…

常用三极管汇总

常用三极管汇总 一、插件三极管 型号基本参数类型封装应用说明实物图PcVCBOVCEOVEBOhFEIC2SC26550.9W50V50V5V--2ANPNTO-92L高速开关管&#xff0c;用于大电流PWM推挽驱动与2SA1020互补 2SC90130.625W40V20V5V84-2020.5ANPNTO-92与SS9012互补同KSP2222A2N55510.63W180V160V6V3…