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

由于 Java 虚拟机的多线程是通过轮流分配 CPU 时间片的方式来实现的,因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。

那么程序计数器里存的到底是什么东西呢?

《深入理解 Java 虚拟机:JVM 高级实践与最佳实战 - 第 2 版》给出了答案:如果线程正在执行的是一个 Java 方法,程序计数器中记录的就是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器值则为空(Undefined)

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

接下来,我们就通过 DEBUG 这段代码来看下线程的运行原理:

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

上述代码的逻辑非常简单,main 方法调用了 method1 方法,而 method1 方法又调用了 method2 方法。

看下图,我们打了一个断点:

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

OK,以 DEBUG 的方式运行 Test.main(),虽然这里我们没有显示的创建线程,但是 main 函数的调用本身就是一个线程,也被称为主线程(main 线程),所以我们一启动这个程序,就会给这个主线程分配一个虚拟机栈内存。

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

上文我们也说了,虚拟机栈内存其实就是个壳儿,里面真正存储数据的,其实是一个一个的栈帧,每个方法都对应着一个栈帧

所以当主线程调用 main 方法的时候,就会为 main 方法生成一个栈帧,其中存储了局部变量表、操作数栈、动态链接、方法的返回地址等信息。

各位现在可以看看 DEBUG 窗口显示的界面:

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

左边的 Frames 就是栈帧的意思,可以看见现在主线程中只有一个 main 栈帧;

右边的 Variables 就是该栈帧存储的局部变量表,可以看到现在 main 栈帧中只有一个局部变量,也就是方法参数 args。

接下来 DEBUG 进入下一步,我们先来看看 DEBUG 界面上的每个按钮都是啥意思,总共五个按钮(已经了解的各位可以跳过这里):

1)Step Over:F8

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

程序向下执行一行,如果当前行有方法调用,这个方法将被执行完毕并返回,然后到下一行

2)Step Into:F7

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

程序向下执行一行,如果该行有自定义方法,则运行进入自定义方法(不会进入官方类库的方法)

3)Force Step Into:Alt + Shift + F7

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

程序向下执行一行,如果该行有自定义方法或者官方类库方法,则运行进入该方法(也就是可以进入任何方法)

4)Step Out:Shift + F8

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

如果在调试的时候你进入了一个方法,并觉得该方法没有问题,你就可以使用 Step Out 直接执行完该方法并跳出,返回到该方法被调用处的下一行语句。

5)Drop frame

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

点击该按钮后,你将返回到当前方法的调用处重新执行,并且所有上下文变量的值也回到那个时候。只要调用链中还有上级方法,可以跳到其中的任何一个方法。


OK,我们点击 Step Into 进入 method1 方法,可以看到,虚拟机栈内存中又多出了一个 method1 栈帧:

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

再点击 Step Into 直到进入 method2 方法,于是虚拟机栈内存中又多出了一个 method2 栈帧:

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

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

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

然后点击 Step Over 执行完输出语句(Step Into 会进入 println 方法,Force Step Into 会进入 Object.toString 方法)

至此,method1 的使命全部完成,method1 栈帧会从虚拟机栈内存中被销毁。

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

最后再往下走一步,main 栈帧也会被销毁,这里就不再贴图了。

线程运行原理详细图解

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

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