文章目录
- 1. JVM运行时数据区(内存区域划分)
- 2. JVM类加载机制
- 常见面试题
1. JVM运行时数据区(内存区域划分)
JVM运行时数据区域也叫内存布局,但需要注意的时它和Java内存模型(JMM)不同,属于不同的概念。
JVM运行时数据区域由下列组成
-
程序计数器:内存中最小的区域,保存了下一条指令的地址。
JVM将字节码加载到内存,程序会将指令从内存中取出,放到CPU上执行。
程序计数器就是用来标记当前执行到第几条指令。
每个线程都有一份。 -
栈:存放局部变量和方法调用信息
每次调用方法时入栈,执行完方法后出栈操作。
栈中的每一个元素称为栈帧。
每个线程都有一份。 -
堆:一个进程只有一份,多个线程共用一个堆。
堆中的内容保存new出来的对象。
(局部变量在栈上,成员变量在堆上。) -
方法区:方法区中存放的是类对象
java文件在运行时会被编译为class文件,class文件被加载到内存中,被JVM构造成类对象(加载的过程称为类加载)
这个类对象就放在方法区中。
类对象中包含(静态成员(static修饰的成员),类名称,成员变量和方法等)
2. JVM类加载机制
类加载:把class文件加载到内存中,构成对象。
整个类加载分为三大步
loading(加载) , linking(连接) , initializing(初始化)
loading:找到对应的.class文件,打开.class文件,生成一个类对象。
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态储存结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.long.Class对象,作为方法区这个类的各种数据的访问入口。
linking:建立好多个实体之间的联系。
-
验证:保证读取的.class数据内容是否符合规范,是否正确。如果验证失败,就会类加载失败,并且抛出异常。
-
准备:为静态变量分配内存,并且设置0值。
-
解析:对常量池的符号引用替换为直接引用的过程,初始化常量
.class文件中,常量是集中放置的,每个常量有一个编号。
.class文件内的结构体初始情况只是记录常量编号
解析的过程就是将这个编号还原为变量的过程
initializing:对类对象进行初始化
常见面试题
- 下面代码的运行结果
class A {
public A() {
System.out.println("A的构造函数");
}
{
System.out.println("A的构造代码块");
}
static {
System.out.println("A的静态代码块");
}
}
class B extends A {
public B() {
System.out.println("B的构造函数");
}
{
System.out.println("B的构造代码块");
}
static {
System.out.println("B的静态代码块");
}
}
public class TestJVM extends B {
public static void main(String[] args) {
new TestJVM();
new TestJVM();
}
}
原则:
- 类加载阶段进行静态代码块执行
- 静态代码块旨在类加载过程中执行一次
- 构造方法和构造代码块每次实例化都会执行,构造代码块在构造方法前
- 先初始化父类,之后初始化子类。加载顺序类似。
要想执行main方法,需要先加载TestJVM类。
要想加载TestJVM类就要加载B类,要加载B类就要加载A类。
- 双亲委派模型
处于类加载中的加载阶段,描述的JVM类加载器如何根据类的全限定名找到.class文件的过程
JVM里提供专门的对象负责类加载(类加载器)
.class文件可能存放到多个位置,JVM里面提供多个类加载器,每个类加载器负责一个片区
默认的类加载器:
- BootStrapClassLoader:负责标准库中的类
- ExtensionClassLoader:负责加载JDK扩展的类
- ApplicationClassLoader:负责加载当前项目目录的类
也可以自定义类加载器,用来加载其他目录的类。
双亲委派模型:描述了类加载器找目录的过程。
eg:类加载器加载java.long.String类
- 程序启动,先进入ApplicationClassLoader类加载器
- ApplicationClassLoader检查它的父加载器是否已经加载过,如果没有,就调用父加载器ExtensionClassLoader
- ExtensionClassLoader检查父加载器是否加载过,如果没有就调用父加载器BootStrapClassLoader
- BootStrapClassLoader扫描负责的目录
- java.long.String这个类在BootStrapClassLoader负责的目录下找到了,所以BootStrapClassLoader负责后序的类加载过程。加载过程结束。
eg:考虑加载用户自定义的test类
前4个步骤于上面相同。
- test这个类在BootStrapClassLoader负责的目录下没找到,回到子加载器继续加载。
- ExtensionClassLoader扫描自己负责的目录,也没找到
- ApplicationClassLoader扫描自己负责的目录,找到test类,进行类加载。
- 如果ApplicationClassLoader也没找到,就抛出类没找到异常。
这样设计,一旦用户自定义的类和标准库中的类名重复了,也可以加载到标准库的类。保证加载类的唯一性。
自定义的类加载器可以选择不遵守双亲委派模型
eg:tomcat加载webapp中的类,没有遵守双亲委派模型。