7.4.2. 方法区的内部结构
类型信息
对每个加载的类型(类 class、接口 interface、枚举 enum、注解 annotation),JVM 必须在方法区中存储以下类型信息:
这个类型的完整有效名称(全名=包名.类名)
这个类型直接父类的完整有效名(对于 interface 或是 java.lang.object,都没有父类)
这个类型的修饰符(public,abstract,final 的某个子集)
这个类型直接接口的一个有序列表
域(Field)信息
JVM 必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient 的某个子集)
问题:方法区包含域信息,也就是包含成员变量?
只是存放了成员变量的信息,具体的值会和对象一起放在堆中。类变量会存放在方法区
方法(Method)信息
JVM 必须保存所有方法的以下信息,同域信息一样包括声明顺序:
方法名称
方法的返回类型(或 void)
方法参数的数量和类型(按顺序)
方法的修饰符(public,private,protected,static,final,synchronized,native,abstract 的一个子集)
方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract 和 native 方法除外)
异常表(abstract 和 native 方法除外)
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
**备注:**以上代码皆来源于MethodInnerStrucTest 类的字节码文件, 可以通过 javap -v -p Met hodInnerStrucTest.class > test.txt 命令,将字节码导出到文件中,便于观察.
字节码文件中存放着类文件的类型详细信息.(如上所示)
类加载器将字节码文件加载到方法区后. 方法区中除了记录着类的详细信息,还包括该类是被哪个类加载器加载的等, 同时类加载器(也是类)也会记录加载了哪些类.
non-final 的类变量
静态变量和类关联在一起,随着类的加载而加载,他们成为类数据在逻辑上的一部分
类变量被类的所有实例共享,即使没有类实例时,你也可以访问它
/** * non-final的类变量 * @author shkstart shkstart@126.com * @create 2020 20:37 */ public class MethodAreaTest { public static void main(String[] args) { Order order = null; order.hello(); System.out.println(order.count); } } class Order { public static int count = 1; public static final int number = 2; public static void hello() { System.out.println("hello!"); } }
执行以上的main方法,并不会报错,amazing!
补充说明:全局常量(static final)
被声明为 final 的类变量的处理方法则不同,每个全局常量在编译的时候就会被分配了。
通过字节码查看
代码同上面....
对比final修饰的变量赋值操作
查看静态变量赋值操作
7.4.3. 运行时常量池 VS 常量池
方法区,内部包含了运行时常量池
字节码文件,内部包含了常量池
要弄清楚方法区,需要理解清楚 ClassFile,因为加载类的信息都在方法区。
要弄清楚方法区的运行时常量池,需要理解清楚 ClassFile 中的常量池。
官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述符信息外,还包含一项信息就是常量池表(Constant Pool Table),包括各种字面量和对类型、域和方法的符号引用. 也就是说一个方法的具体实现细节都藏在常量池中
举例:
常量池中的信息相当于炒菜的基本原料,每个方法相当于一道道菜.每道菜都可能会用到那些基本原材料.对应到代码中,方法的字节码会用到常量池中的内容
为什么需要常量池?
一个 java 源文件中的类、接口,编译后产生一个字节码文件。而 Java 中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。在动态链接的时候会用到运行时常量池,之前有介绍。
比如:如下的代码:
public class SimpleClass { public void sayHello() { System.out.println("hello"); } }
虽然只有 194 字节,但是里面却使用了 String、System、PrintStream 及 Object 等结构。这里的代码量其实很少了,如果代码多的话,引用的结构将会更多,这里就需要用到常量池了。
常量池中有什么?
击中常量池内存储的数据类型包括:
数量值
字符串值
类引用
字段引用
方法引用
例如下面这段代码:
public class MethodAreaTest2 { public static void main(String args[]) { Object obj = new Object(); } }
Object obj = new Object();
将会被翻译成如下字节码:
0: new #2 // Class java/lang/Object 1: dup 2: invokespecial // Method java/lang/Object "<init>"() V
小结
常量池、可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型