对JVM内部组成结构、栈帧、GC的简单理解(精简)
话不多说奔主题,精神抖擞就是干!
1. 先来说下这张图

上图下面两行注释部分:
绿色代表所有线程共享区域
白色代表每个线程独享区域
简单讲:JVM中包含
ClassLoader:类加载子系统,即加载class字节码文件用的。
ExecutionEngine:字节码执行引擎,包括给GC分配执行线程。
RuntimeDataArea:运行时数据区。其中又包含了,
MethodArea:方法区,以前习惯叫永久代、持久代,JDK1.8之后叫元空间(它使用的是操作系统物理内存,而非JVM内存)。方法区其实不仅包含方法,还有常量(即字面量)、静态变量、类信息、运行时常量池等。
Heap:线程堆,堆里面一般存放我们new出来的对象。
JavaStack:java栈,栈里面一般存放临时变量,比如局部变量。它为每个线程提供独立的一块执行java代码的栈空间。
NativeMethodStack:本地方法栈,主要是对本地操作系统提供的方法库(例如:Windows下的dll文件)的接口封装,现在都流行微服务,HTTP调用,所以现在这块已经不常用了。
ProgramCounterRegister:程序计数器简称PC,对字节码文件执行行数的记录。例如:当一个线程在执行字节码文件中的指令方法时可能因为CPU分配给该线程的时间片到期了,需要暂停当前任务给其他线程让行,那么需要有人来记录下我当前任务执行到哪了(你可以理解为执行到哪一行代码了),以便我下次从个位置继续执行。
2. 线程栈,栈帧
假如我们写了这样一个java文件
Test.java
public class Test {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
编译成字节码文件
命令行:javac Test.java
输出:Test.class
用文本编辑器打开,大概是长这样的👇

这尼玛啥玩意啊,看不懂啊,别急我们用专业工具看下👇

是不是很眼熟,这不就是我们写的java程序吗?!
再用命令行:javap -c -l Test.class(或者 javap -v Test.class)// -l 参数是显示行号,你可以理解为就是上面提到过的程序计数器记录的行号,还记得吗?艾玛,真香!
看下反汇编结果👇

👆这就是我们常说的JVM指令码。
我们知道要运行main方法其实也是需要线程来执行的,一般是主线程来执行。既然是线程就该具备一个线程该有的东西。
那么,究竟一个线程里包含哪些东西呢?往下看👇
|---------线程---------|
| 程序计数器 |
|
线程栈(含有栈帧) main方法即栈帧 ... ... |
| 本地方法栈 |
关于栈帧,我懒得写了,这里推荐博客园里yulibo写的一篇关于栈帧的文章
https://www.cnblogs.com/yulibostu/articles/8944806.html
3. GC
英文释义:Garbage Collect
中文释义:垃圾回收
在JVM堆中,有着这样的划分👇
Eden:伊甸区
Survivor:幸存区
old :老年区,即老年代
其中,
伊甸区和幸存区属于年轻代。
幸存区又分为s0区和s1区。
假如我们的JVM堆有300M空间
默认情况下(也可以通过命令手动指定各区域大小,这里就涉及到JVM内存优化的知识了)各区域对堆空间的划分比例是多少?
年轻代 :老年代 = 1 : 2,即100M和200M。
伊甸区 :幸存区 = 4 : 1,即80M和20M。//伊甸区:s0:s1 = 8 : 1 : 1
s0 :s1 = 1 : 1,即10M和10M。
现在说说GC回收过程,简陋的区域划分图👇
伊甸区 幸存去 老年区
| Eden | s0 | s1 | old |
1. new出来的对象首先肯定是进入到伊甸区,当伊甸区住满了以后,会触发一次minorGC,这时有一些游离对象会被当场杀死(释放掉),而存活下来的对象(仍被引用的对象),会被划分到s0区,并且分代年龄(存在于对象头ObjectHeader中)增加1。伊甸区这时已经空了。
2. 当伊甸区再次住满时,又会再次触发minorGC,这时又有一些游离对象(伊甸区和s0区)会被当场杀死(释放掉),而存活下来的对象(伊甸区和s0区仍被引用的对象),会被一起划分到s1区,并且分代年龄增加1。伊甸区和s0区这时已经空了。
3. 当伊甸区又再次住满时,又会再次触发minorGC,这时又又有一些游离对象(伊甸区和s1区)会被当场杀死(释放掉),而存活下来的对象(伊甸区和s1区仍被引用的对象),会被一起划分到s0区,并且分代年龄增加1。伊甸区和s1区这时已经空了。
4. 当这些幸存下来的对象年龄到达15岁时(默认,可通过 -XX:MaxTenuringThreshold 修改),他们会被丢到老年区等死。
5. 当老年区到达饱和时,会触发一次fullGC(会对老年代和年轻代进行垃圾对象搜集回收,此过程非常耗时),这时一些没人认领的老不死(游离对象)会被直接丢进火葬场。
上述2,3,4,5步骤只要满足条件会循环往复的执行,每次minorGC时都会先计算老年代的剩余内存空间。
*各单位注意,以下情况除外:
1. 每次minorGC之后幸存下来的对象,指伊甸区+放置对象的那个s区的总大小超过幸存区的内存空间时,会被直接丢进老年区。
2. 每次minorGC之后,如果幸存区(s0或s1当前有放置对象的那个区)放置的对象的总大小超过幸存区内存空间的50%时(默认,可通过 -XX:TargetSurvivorRatio 修改),那么这批对象里年量较大一批对象会被直接丢进老年区。
4. GCRoot根节点
GC是如何找到所有被直接或间接引用到的对象的呢?
答:就是根据GCRoot根节点,不断向下搜索(可达性分析算法),被找到的对象会被JVM复制算法复制到幸存区中的非空区域,
而其余对象被标记为垃圾对象后直接干掉。
*注:我上述提到的所谓的游离对象就是垃圾对象,就是根据根结点无法找到的对象,这些对象没有被任何其他变量引用。
哪些变量可以看作是根节点呢?
答:线程栈本地变量、静态变量、本地方法栈的变量等。
5. STW
英文释义:Stop The World
中文释义:时停 //JoJo,我真是嗨到不行啊~
它其实就是在GC时,会停止所有正在执行的用户线程的一种现象。
为什么要停止所有用户线程?
因为如果不停止,那么GC的时候对象的引用状态就不能被确定,比如有些被判定为非游离态的对象在线程执行过程中变为游离态了,那我GC之前判定的结果是不是就不对了,难道我又重新计算一次?这不是陷入死循环了吗,这还玩儿个屁啊。
6. 最后
珍爱生命,减少fullGC,让程序飞起来~
介绍一个JDK自带的诊断工具
命令行:jvisualvm
可以通过 Visual GC(该插件需要额外安装) Tab页查看进程的GC的动态图形过程。
欢迎看官儿们留言补充和指正,谢谢下次见!

浙公网安备 33010602011771号