图中运行时数据区,红色部分线程共享,具有线程安全问题;灰色部分每个线程独一份,不存在线程安全问题。
目录
0. 你将获得什么?
- 方法的执行步骤,以及这些步骤中是如何利用运行时数据区的。
1. 从方法执行开始讲起
我们在上一篇中讲解了JVM的启动过程,其中有一步骤是执行方法,那么我们就从方法是如何执行的引入吧。
public class A {
public static void main(String[] args) {
B b = new B();
String str = b.getStr();
System.out.println(str);
}
}
public class B {
public String getStr(){
return "hello world";
}
}
假如我们有以上的两个类,那么JVM是如何运行的呢?
- 首先按照JVM的启动顺序,①初始化内存②新建BootstrapClassLoader加载系统类③新建JVM实例Lancher并初始化AppClassLoader。(这部分具体内容详解,请参看本专栏的这篇文章)
- 将B.class中所有的信息加载到方法区(通过类加载子系统)
- 将A.class中所有的信息加载到方法区(通过类加载子系统)
- 在堆中创建Class<A>实例
- 执行Class<A>实例的main方法,创建栈帧压入本线程的虚拟机栈。目前为止运行时数据区如下图所示:
- 读取PC指向的机器指令,开始执行。
- PC+1,执行下一条指令。
2. 运行时数据区
在上述过程中,和运行时数据区有关的我都使用红色标记,。我们发现程序的运行和运行时数据区密不可分,每个过程都有运行时数据区的参与。
2.1 方法区
2.1.1 方法区存放什么?
存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等。
- 类信息(类型信息):由.class文件经过类加载子系统的loading、linking、initialization而来,这部分参阅这篇文章。
(i) 类的完整有效名
(ii) 直接父类的完整有效名(除非是当前类型为interface 或者 java.lang.Object,两者都没有父类)
(iii) 类型的修饰符(public、abstract、final等) - 方法信息:方法的修饰符、返回值类型、方法名称、方法参数列表、方法异常表、方法字节码、操作数栈以及局部变量表的大小(编译时期就可以确定了)。
- 变量信息(类变量):非final类变量(final类变量在编译时期已经确定,保存在常量池中)
- 类型的常量池:里面放着编译时期确定的值和符号引用。这部分请参看这篇文章。
- 对类加载器的引用:jvm必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的
- 对Class类的引用:
- 方法表:java语言始终是把安全放在首位,难免牺牲效率为代价的,为了提高应用效率jvm还添加了方法表,jvm可以通过方法表快速激活实例方法。
所以方法区在运行时大概是长成这样的:
ps:这里的两个类都是由BootstrapClassLoader加载的,而BootstrapClassLoader是由c写的,因此类加载器引用指向为null。
2.1.2 总结
请记住方法区存放的是与类有关的信息即可。
2.2 ⭐堆
堆是JVM中最重要的内存结构,垃圾回收器主要的工作区域也是堆。
2.2.1 堆中存放什么?
主要存放实例对象以及数组。
2.3 栈(虚拟机栈)
虚拟机栈是线程私有的,与线程的生命周期一致。
2.3.1 栈帧的结构
每个方法在执行时都会创建一个栈帧,栈帧具有如下结构:
- 局部变量表
- 操作数栈(没错,虚拟机栈的栈帧里面还有一个操作数栈,不要搞混哦~)
- 动态链接
- 方法出口
本文是《JVM看这篇就够了!》专栏的一部分,如果觉得对您有帮助的话,不妨给作者点一个赞。