目录

一、堆的核心概述: 

二、堆内存划分

1、图解

2、堆内存划分

3、jdk 7和jdk 8逻辑上堆内存的划分

三、年轻代与老年代

四、设置堆内存大小与 OOM(了解)

1、设置堆空间大小

2、代码示例

五、图解对象分配过程

六、Minor GC、Major GC、Full GC

1、JVM调优-垃圾回收

2、部分收集(Minor GC/Major GC)

3、整堆收集(Full GC)

4、年轻代 GC(Minor GC)触发机制

5、老年代 GC(MajorGC/Full GC)触发机制

七、堆空间分代思想

1、分代机制

2、为什么要分代?


一、堆的核心概述: 




--jvm架构图




java jvm 非堆内存 jvm的堆_java


1、进程和JVM  


线程共享:堆、方法区


一个进程对应一个JVM的实例,一个JVM实例中只有一个运行时数据区,里面只有一个方法区和堆,一个进程的多个线程共享方法区和堆,那就要考虑线程的安全问题。


每个线程各有一套程序计数器、本地方法栈、虚拟机栈。


2、一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。


3、Java堆区在JVM启动的时候即被创建,其空间大小也就确定了,堆是JVM管理的最大一块内存空间,并且堆内存的大小是可以调节的。


4、《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。


5、所有的线程共享Java堆,要考虑线程的安全问题,并发性就差


解决:在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB),提高并发性。


6、《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is allocated)


7、数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。


8、在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。也就是触发了GC的时候,才会进行回收。


为什么不能频繁触动GC?如果栈里面对象引用一出栈,堆里面的对象立马GC,会造成GC的频率特别高,会影响用户线程的执行


堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。


(注意:虚拟机栈没有GC,只有入栈和出栈,方法区也有垃圾回收:有些类根本不用了,要回收)


图解--加载类到方法区,在jvm生成实例对象,new出的对象放在堆,局部变量在栈


java jvm 非堆内存 jvm的堆_jvm_02


10、堆的大小


一个JVM实例只存在一个堆内存,并且堆内存的大小是可以调节的


如何设置堆内存大小


-Xms10m -Xmx10m


Xms:堆区的起始内存


Xmx:堆区的最大内存


改变堆内存大小,使Visual VM演示动画更加明显


java jvm 非堆内存 jvm的堆_jvm_03


演示代码块


java jvm 非堆内存 jvm的堆_java jvm 非堆内存_04


在Visual VM得到动图


java jvm 非堆内存 jvm的堆_jvm_05


堆空间设置的大小=新生代+老年代,不包括元空间。


java jvm 非堆内存 jvm的堆_老年代_06


java jvm 非堆内存 jvm的堆_老年代_07


二、堆内存划分

1、图解


    Visual VM图解


java jvm 非堆内存 jvm的堆_老年代_08


2、堆内存划分


老年代


新生代:


    Eden空间


    Survivor0空间和Survivor1空间


3、jdk 7和jdk 8逻辑上堆内存的划分


图一


java jvm 非堆内存 jvm的堆_java_09


图二


java jvm 非堆内存 jvm的堆_开发语言_10




三、年轻代与老年代


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


  • 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速,生命周期短的,及时回收即可
  • 另外一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致


2、Java堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(oldGen)


其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区、to区,这两个区的大小是相等的)


年轻代和老年代详细划分


java jvm 非堆内存 jvm的堆_java jvm 非堆内存_11


    分区目的:方便垃圾回收


3、配置新生代与老年代的比例 (一般不会调)(记住比例)


java jvm 非堆内存 jvm的堆_jvm_12


  • 默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
  • 可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5

调优:当发现在整个项目中,生命周期长的对象偏多,那么就可以通过调整老年代的大小,来进行调优


4、新生区中的比例


  • 在HotSpot中,Eden空间和另外两个survivor空间缺省所占的比例是8 : 1 :1
  • 当然开发人员可以通过选项-XX:SurvivorRatio调整这个空间比例。比如-XX:SurvivorRatio=8
  • 几乎所有的Java对象都是在Eden区被new出来的。绝大部分的Java对象的销毁都在新生代进行了(有些大的对象在Eden区无法存储时候,将直接进入老年代,比如新生代快满了,剩下的空间不够新对象,这个对象直接进入老年代)
  • IBM公司的专门研究表明,新生代中80%的对象都是“朝生夕死”的。
  • 新生代的对象默认生命周期超过 15 ,就会去养老区养老(垃圾回收15次后还没有销毁,就进入老年代)


java jvm 非堆内存 jvm的堆_老年代_13




四、设置堆内存大小与 OOM(了解)


1、设置堆空间大小


1、Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,大家可以通过选项"-Xms"和"-Xmx"来进行设置。


    -Xms用于表示堆区的起始内存,等价于-XX:InitialHeapSize


    -Xmx则用于表示堆区的最大内存,等价于-XX:MaxHeapSize


2、一旦堆区中的内存大小超过“-Xmx"所指定的最大内存时,将会抛出OutofMemoryError异常。


3、通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在Java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能。


4、默认情况下:


    初始内存大小:物理电脑内存大小/64


    最大内存大小:物理电脑内存大小/4


2、代码示例


2.1


--验证堆空间的大小 和推断当前系统内存的大小


java jvm 非堆内存 jvm的堆_开发语言_14


运行结果


-Xms : 123M


-Xmx : 1801M


系统内存大小为:7.6875G


系统内存大小为:7.03515625G


2.2


--不停的创建对象加到集合中会引发OOM异常


java jvm 非堆内存 jvm的堆_java_15


OOM异常


java jvm 非堆内存 jvm的堆_老年代_16


● 监控堆内存变化:Old 区域一点一点在变大,直到最后一次垃圾回收器无法回收垃圾时,堆内存被撑爆,抛出 OutOfMemoryError 错误


堆内存变化图


● 分析原因:大对象导致堆内存溢出




五、图解对象分配过程


1、我们创建的对象,一般都是存放在Eden区的,当我们Eden区满了后,就会触发GC操作,一般被称为 YGC / Minor GC操作。


(Eden和S0、S1的比例是8:1:1,只有Eden满了才出发YGC,S0满了不会触发YGC,但是触发YGC时候会把Eden和S0一起回收。)


java jvm 非堆内存 jvm的堆_java_17


2、当我们进行一次垃圾收集后,红色的对象将会被回收(不再被使用,没有引用指向他),而绿色的还被使用着,存放在S0(Survivor From)区。


    同时我们给每个对象设置了一个年龄计数器,经过一次回收后还存在的对象,将其年龄加 1。


3、同时Eden区继续存放对象,当Eden区再次存满的时候,又会触发一个MinorGC操作,此时GC将会把 Eden和Survivor From中的对象进行一次垃圾收集,


     把存活的对象放到 Survivor To(s1)区,同时让存活的对象年龄 + 1,现在S1是From区,S0是To区(经过一次MinorGC后谁空谁就是To区)。


java jvm 非堆内存 jvm的堆_老年代_18


4、我们继续不断的进行对象生成和垃圾回收,当Survivor中的对象的年龄达到15的时候,将会触发一次 Promotion 晋升的操作,也就是将年轻代中的对象晋升到老年代中.


可以设置新生区进入养老区的年龄限制,设置 JVM 参数:-XX:MaxTenuringThreshold=N 进行设置


java jvm 非堆内存 jvm的堆_老年代_19


在养老区,相对悠闲。当养老区内存不足时,再次触发GC:Major GC,进行养老区的内存清理


若养老区执行了Major GC之后,发现依然无法进行对象的保存,就会产生OOM异常。


图解对象分配流程图


java jvm 非堆内存 jvm的堆_java jvm 非堆内存_20


总结:


1、针对幸存者s0, s1区的总结:交换之后,谁空谁是to.


2、关于垃圾回收:频繁在新生代收集,很少在老年代收集,几乎不在永久区/元空间收集


永久区/元空间是方法区的落地实现




六、Minor GC、Major GC、Full GC


1、JVM调优-垃圾回收

  • 我们都知道,JVM的调优的一个环节,也就是垃圾收集,我们需要尽量的避免垃圾回收,因为在垃圾回收的过程中,容易出现STW(Stop the World)的问题,会停掉用户线程,而 Major GC 和 Full GC出现STW的时间,是Minor GC的10倍以上
  • JVM在进行GC时,并非每次都对上面三个内存区域(新生代、老年代、方法区)一起回收的,大部分时候回收的都是指新生代。

     针对Hotspot VM的实现, 它里面的GC按照回收区域又分为两大种类型: 一种是部分收集(Partial GC),一种是整堆收集(FullGC)


2、部分收集(Minor GC/Major GC)


不是完整收集整个Java堆的垃圾收集。其中又分为:


  • 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
  • 老年代收集: (Major GC/Old GC):只是老年代的圾收集。


3、整堆收集(Full GC)


收集整个java堆和方法区的垃圾收集。(java堆就包含新生代和老年代)


4、年轻代 GC(Minor GC)触发机制

  • 当年轻代空间不足时,就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。(每次Minor GC会清理年轻代的内存)
  • 因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。
  • Minor GC会引发STW(stop the word),暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行,一般回收速度也比较快,所以对用户线程影响比较小,影响较大的是老年代GC,STW时间较长。


5、老年代 GC(MajorGC/Full GC)触发机制

  • 指发生在老年代的GC,对象从老年代消失时,我们说 “Major Gc” 或 “Full GC” 发生了
  • 出现了MajorGc,经常会伴随至少一次的Minor GC,但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行MajorGC的策略选择过程也就是在老年代空间不足时,会先尝试触发Minor GC,如果之后空间还不足,则触发Major GC
  • Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长,如果Major GC后,内存还不足,就报OOM了




七、堆空间分代思想


--优化GC


1、分代机制


为什么要把Java堆分代?不分代就不能正常工作了吗?


经研究,不同对象的生命周期不同。70%-99%的对象是临时对象。


  • 新生代:有Eden、两块大小相同的survivor(又称为from/to,s0/s1)构成,to总为空。
  • 老年代:存放新生代中经历多次GC仍然存活的对象。长期存活的对象分配到老年代


2、为什么要分代?


其实不分代完全可以, 分代的唯一理由就是优化GC性能


  • 如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。GC的时候要找到哪些对象没用,这样就会对堆的所有区域进行扫描。
  • 而很多对象都是朝生夕死的,如果分代的话,把新创建的对象放到某一地方,当GC的时候先把这块存储“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。


Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行,一般回收速度也比较快,所以对用户线程影响比较小,

 影响较大的是老年代GC,STW时间较长,对GC的优化减少GC出现的频率


就像疫情的核酸检测,将城阳区人群分为:1、本地没出去的人 2、疫区回来的人 3、得过新冠的人,如果都每隔两天进行全员核酸检测代价太大,

对本地可以十天一次核酸检测,疫区回来的一周一次,得过的三天一次。


java jvm 非堆内存 jvm的堆_jvm_21