🏝️ 博主介绍
大家好,我是 一个搬砖的农民工,很高兴认识大家 😊 ~
👨🎓 个人介绍:本人是一名后端Java开发工程师,坐标北京 ~
🎉 感谢关注 📖 一起学习 📝 一起讨论 🌈 一起进步 ~
🙏 作者水平有限,欢迎各位大佬指正留言,相互学习进步 ~
目录
🌱 JVM是Java中最核心的概念之一,本文将按照以下思维导图的结构,深入讲解Java虚拟机(JVM)的核心概念 🍂
一、JVM内存结构 🚀
在网上借鉴几张图片,可以很形象看出jvm的内存结构
🌈 1、堆(Heap)
堆是JVM内存中最大的一块,用来存储对象和数组,它被所有线程共享
。
🍨 (1)特点
- 通过 new 关键字,创建的对象都会使用堆内存,数组和字符串常量池(StringTable)也存储在堆中
- 它是线程共享的
- 堆中对象都需要考虑线程安全的问题,有垃圾回收机制
🍨 (2)堆内存分配
在 Java 的堆内存中,可以分配为新生代
和老年代
的主要依据是对象的生命周期。这个分配是为了更好地进行垃圾回收和提高内存利用率。默认分配比例如下:
-
⭐ 新生代(Young Generation): 新生代由
伊甸园(Eden Space)
和两个幸存者区(Survivor Space)
组成。 -
⭐ 伊甸园(Eden Space):伊甸园是新生代中的一部分,用于存放
新创建的对象
。大部分对象在伊甸园中被创建。当内存需要分配给新对象时,大部分对象都会首先被放入伊甸园中。 -
⭐ 幸存者区(Survivor Space):幸存者区包括两个区域,分别为
From区
和To区
。幸存者区的数据是在 From 区和 To 区之间进行交换的。
例如:当from区和to区都是null的时候,第一次从新生代eden进行垃圾回收,会把存活下来的对象放入from区,下次垃圾回收会把存活下来的数据放入to区,然后from区清空。再下次垃圾回收会把存活下来的数据放入from区,然后to区清空。直到达到一定的年龄后,这些对象会被晋升到老年代。 -
⭐ 老年代(Old Generation):用于存放新生代中经过多次gc依然存活的对象,或者新生代中放不下的大对象。
🍨 (3)晋升到老年代的方式
- ⭐ 年龄阈值:当对象在 survivor 区存活了 15 次(默认)之后,会被移到老年代区。可以通过JVM参数
-XX:MaxTenuringThreshold
修改。 - ⭐ 动态对象年龄判定:动态对象年龄判定:当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄。
- ⭐ survivor空间不足:当存活下来的对象大于survivor区容量的时候,会被移到老年代区。
假设新生代由100MB的Eden空间和两个50MB的Survivor空间组成,老年代有500MB的空间。
初始情况下,所有新创建的对象都分配在Eden空间。
进行第一次GC,此时Eden空间有80MB的对象,被GC后只有30MB的对象存活。这些存活的对象被移动到Survivor1,Eden被清空。
再次分配对象,Eden空间再次填满到80MB,此时Survivor1中还有30MB的存活对象。
进行第二次GC,Eden区的80MB对象中,60MB存活,加上Survivor1中的30MB存活对象,一共有90MB需要被移动到Survivor2,但Survivor2只有50MB的容量。
此时,JVM会检查Survivor1中对象的年龄,并将年龄大的对象提前晋升到老年代
,假设10MB的对象被晋升,这样剩下20MB的对象与Eden区的60MB存活对象能够被移动到Survivor2。
如果Survivor空间依旧不足以处理这60MB的对象,那么无论年龄如何,都会将多出来的部分提前晋升到老年代
。
GC的这些细节实际上取决于使用的垃圾收集器以及JVM的配置参数,不同的垃圾收集器(如Serial, Parallel, CMS, G1,
ZGC等)会以不同的方式管理这些区域。
🍨 (4)堆内存检验方式
✨ 1、jmap
- 首先使用
jps
查看有哪些进程 - 然后根据
jmap -heap [进程ID]
查看进程的堆内存
实例:
new一个10M的字节对象,来占用堆内存,在输出分别在输出 1 2 3后打出 jmap命令对比堆内存变化
public class Test {
public static void main(String[] args) throws InterruptedException {
System.out.println("1111111111111111111111111");
Thread.sleep(20000);
byte[] array = new byte[1024 * 1024 * 10]; // 10M内存
System.out.println("2222222222222222222222222");
Thread.sleep(20000);
System.gc(); // 垃圾回收
System.out.println("3333333333333333333333333");
Thread.sleep(10000);
}
}
打出 111111 后先根据 jps
命令查看到进程id
23968 Test
3312
24196 Jps
22764 Launcher
4828 RemoteMavenServer36
可以看出启动类Test进程ID是23968,然后输入命令:jmap -heap 23968
Heap Usage:
PS Young Generation
Eden Space:
capacity = 66584576 (63.5MB)
used = 8754440 (8.348884582519531MB) // 这里只展示部分打印信息,可以看见这里最初占用了8M
13.14784973625123% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
然后控制台打印22222222后,继续输入命令:jmap -heap 23968
Heap Usage:
PS Young Generation
Eden Space:
capacity = 66584576 (63.5MB)
used = 19240216 (18.348899841308594MB) // 可以看见这里占用内存变成了18M
28.895905261903298% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (