以 DEBUG 方式深入理解线程的底层运行原理 (3)

上面写了这么多,其实也就是教会了大家栈帧这个东西,接下来我们通过图解的方式,来带大家详细看看线程运行时,Java 运行时数据区域的各种变化。

首先第一步,类加载。

《深入理解 Java 虚拟机:JVM 高级实践与最佳实战 - 第 2 版》中是这样解释类加载的:虚拟机把描述类的数据从 Class 文件(字节码文件)加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。

加载进来的这些字节码信息,就存储在方法区中。看下图,这里为了各位理解方便,我就不写字节码了,直接按照代码来,大家知道这里存的其实是字节码就行:

以 DEBUG 方式深入理解线程的底层运行原理

主线程调用 main 方法,于是为该方法生成一个 main 栈帧:

以 DEBUG 方式深入理解线程的底层运行原理

那么这个参数 args 的值从哪里来呢?没错,就是从堆中 new 出来的:

以 DEBUG 方式深入理解线程的底层运行原理

而 main 方法的返回地址就是程序的退出地址。

再来看程序计数器,如果线程正在执行的是一个 Java 方法,程序计数器中记录的就是正在执行的虚拟机字节码指令的地址,也就是说此时 method1(10) 对应的字节码指令的地址会被放入程序计数器,图片中我们仍然以具体的代码代替哈,大家知道就好:

以 DEBUG 方式深入理解线程的底层运行原理

OK,CPU 根据程序计数器的指示,进入 method1 方法,自然,method1 栈帧就被创建出来了:

以 DEBUG 方式深入理解线程的底层运行原理

局部变量表和方法返回地址安顿好后,就可以开始具体的方法调用了,首先 10 会被传给 x,然后走到 y 被赋值成 x + 1 这步,也就是程序计数器会被修改成这步代码对应的字节码指令的地址:

以 DEBUG 方式深入理解线程的底层运行原理

走到 Object m = method2(); 这一步的时候,又会创建一个 method2 栈帧:

以 DEBUG 方式深入理解线程的底层运行原理

可以看到,method2 方法的第一行代码会在堆中创建一个 Object 对象:

以 DEBUG 方式深入理解线程的底层运行原理

随后,走到 method2 方法中的 return n; 语句,n 指向的堆中的地址就会被返回给 method1 中的 m,并且,满足栈后进先出的原则,method2 栈帧会从虚拟机栈内存中被销毁:

以 DEBUG 方式深入理解线程的底层运行原理

根据 method2 栈帧指向的方法返回地址,我们接着执行 System.out.println(m.toString()) 这条输出语句,执行完后,method1 栈帧也被销毁了:

以 DEBUG 方式深入理解线程的底层运行原理

再根据 method1 栈帧指向的方法返回地址,发现我们的程序已走到了生命的尽头,main 栈帧于是也被销毁了,就不再贴图了。

用 DEBUG 的方式看多线程运行原理

上面说的是只有一个线程的情况,其实多线程的原理也差不多,因为虚拟机栈是每个线程私有的,大家互不干涉,这里我就简单的提一嘴。

分别在如下两个位置打上 Thread 类型的断点:

以 DEBUG 方式深入理解线程的底层运行原理

然后以 DEBUG 方式运行,你就会发现存在两个互不干涉的虚拟机栈空间:

以 DEBUG 方式深入理解线程的底层运行原理

当然,使用多线程就不可避免的会遇到一个问题,那就是线程的上下文切换(Thread Context Switch),就是说因为某些原因导致 CPU 不再执行当前的线程,转而执行另一个线程。

导致线程上下文切换的原因大概有以下几种:

1)线程的 CPU 时间片用完

2)发生了垃圾回收

3)有更高优先级的线程需要运行

4)线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdpgs.html