先看这张图:
一 运行时常量池
jvm从字节码中构建类信息,
先解析class文件中的字面量和符号引用到运行时常量池.
运行时常量池是每个类或接口在加载时创建的一个运行时数据结构,存储了编译期生成的各种字面量和符号引用。
字面量(Literals):
- 字符串常量(如 "Hello")。
- 数值常量(如 42 或 3.14)。
- 类型常量(如 true、false、null)。
符号引用(Symbolic References):
- 类的全限定名(如 java.lang.String)。
- 方法的名称和描述符(如 toString() 的方法签名)。
- 字段的名称和描述符(如 int x 的字段签名)。
运行时常量池为 JVM 提供了快速查找类、方法和字段的能力。
它是运行时解析符号引用的基础。
二 Class类元信息
类元信息是指与类本身相关的所有元数据,包括类的名称、父类、接口、字段、方法等。
类的全限定名:类的完整名称(如 java.lang.String)。
父类信息:类的父类(如 Object)。
接口信息:类实现的接口列表。
字段信息:类中定义的所有字段(包括名称、类型、访问修饰符等)。
方法信息:类中定义的所有方法(包括静态方法、实例方法等)。
访问修饰符:类的访问权限(如 public、abstract、final 等)。
注解信息:类上定义的注解(如 @Deprecated)。
类元信息为 JVM 提供了加载、链接和初始化类所需的全部信息。
它是 JVM 管理类生命周期的核心数据结构。
三 方法元信息
方法元信息是指与类的方法相关的所有元数据,包括方法的名称、参数类型、返回类型、访问修饰符等。
存储的内容:
方法名称:方法的名字(如 toString)。
参数类型:方法的参数列表及其类型。
返回类型:方法的返回值类型。
访问修饰符:方法的访问权限(如 public、private、protected)。
字节码:方法的实现代码(即方法体的字节码指令)。
异常表:方法可能抛出的异常列表。
方法元信息为 JVM 提供了执行方法所需的所有必要信息。
它是 JVM 解析和调用方法的基础。
> 说完这三个,其实metaspace就说完了.总结就是,存储了类的基本信息,包括类名,父类,接口,字段,方法(静态方法和实例方法),类中初始化的一些字面量等.
四 再说堆区
有了类,那么就要创建对象.
首先在eden中创建对象,大部分对象都在eden中创建,但是如果对象是大对象,直接进入老年代.
很多对象存活时间都很短,
当eden空间不够的时候会触发新生代GC(minor gc)时采用复制算法,将活着的对象复制到另一个survivor中.所以总有一个survicor是空的. 所以eden和两个survivor的比例是 8:1:1.
多次gc循环后活下来的对象会被移送到老年代中.
Minor GC时,Eden和From区向To区复制时,To区不够大,会直接把对象转移到老年代。
老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。
所以老年代要大一点.默认新生代和老年代的比例是 1:2.
四.1 堆区对象到底有什么
堆区用来存放对象实例!和数组!数组也是特殊的对象.
对象可分为{
1.对象头:{
存储对象的元数据信息:{
当前对象hash值
gc分代年龄
锁状态标识
线程持有的锁信息
}
类指针 class pointer:{}
}
2.实例数据:{
基本类型数据直接存这里
引用类型存引用地址
}
}
> 到这里 堆也讲完了
五 类有了,对象有了,现在开始执行我们的程序了
jvm启动后找到main方法,启动主线程执行main方法 - main thread.(这时候就已经创建了线程对应的虚拟机栈和程序计数器\本地方法栈)
主线程是应用程序的起点,其他线程(如用户创建的线程或框架创建的线程)通常是基于主线程派生出来的.
执行main方法时会创建第一个栈帧,用来存储main方法的
栈帧结构:
局部变量
操作数栈,如在执行加法操作 x + y 时,x 和 y 的值会被压入操作数栈,然后弹出并计算结果。
动态链接,动态链接是一个指针,指向当前方法所属类的运行时常量池,用于解析符号引用。
方法返回地址,因为是main方法,所以没有返回值
main方法栈压入main thread的虚拟机栈后,还会创建一个程序计数器,用来记录当前线程执行的字节码指令的行号.
如果有在当前线程继续调用其他方法,那么就会继续创建该方法的栈帧,并把栈帧压入虚拟机栈中.
如果启动了一个新的线程,那么就会创建该新线程对应的虚拟机栈\程序计数器. 还有本地方法栈用来执行native方法.
> OVER