上面主要分析了方法区以及方法区中的静态区域,下面将主要分析下常量池。常量池主要涉及到常量池里的内容和常量池解析这两块,这篇文章主要分析下常量池概念,大致说下常量池解析,以加深对常量池的理解。
在方法区中,每个类型都对应一个常量池,常量池中存储了诸如文字字符串、final变量值、类名和方法名常量。JVM把常量池组织为入口列表的形式,可通过索引来访问常量池中的各个入口,每个常量池入口的第一个字节都是个标志,用这个标志来表示该入口中存储的常量类型,如CONSTANT_Long表示里面存储的是long类型字面值,CONSTANT_Class_info表示里面存的是某个Class的类型信息(存的可能是个普通的字符串,然后经过常量池解析,则变成指向某个类的引用)。
除了字面常量值以外,常量池还可以容纳其它几种符号引用:类和接口的全限定名、字段名称和描述符、方法名称和描述符。
类和接口的全限定名指的是当前类的全限定名。
字段名称指的是类或接口的实例变量或类变量,字段的描述符是一个指示字段的类型的字符串。如在一个类中有以下形式的声明:A a = null, 则a为字段名,A为字段描述符。
方法的描述符也是个字符串,该字符串指示了方法的返回值和参数的数量、顺序和类型。
在运行时,JVM从常量池中获得符号引用,然后在运行时解析成引用项的实际地址,最后通过常量池中的全限定名、方法和字段描述符,把当前类或接口中的代码与其它类或接口中的代码联系起来。
讲到常量池,就涉及到常量池解析,常量池解析是非常复杂的,它分多种情况处理。这里,就列举个简单的例子,大家看看,大概了解下常量池解析的大概过程。
测试例子程序:
下面将描述下JVM是如何处理Test类中的main()方法的第一条指令:
要运行Test程序,JVM找到对应的Test.class文件,并读入到方法区中。通过保存在方法区中的字节码,JVM开始执行main方法,执行时,它会一直指向当前类(Test类)的常量池。 main的第一条指令告诉JVM要为常量池中的第一项类分配足够的内存,于是JVM使用Test常量池指针找到第一项,发现它是一个对Animal类的符号引用(也就是对字符串“Animal”的引用),于是JVM检查方法区,看Animal类是否被装载了。
如JVM发现还没装载过Animal类时,它开始查找并装载Animal.class文件。
最后,JVM将一个直接指向方法区Animal类数据的指针来代替常量池第一项(也就是那个字符串”Animal”),以后,则可以通过该指针来快速访问Animal类了,这个就称为常量池解析。
说了那么多,常量池解析在这里作的处理就是把常量池中的字符串"Animal"解析成一个对类Animal的引用。
在方法区中,每个类型都对应一个常量池,常量池中存储了诸如文字字符串、final变量值、类名和方法名常量。JVM把常量池组织为入口列表的形式,可通过索引来访问常量池中的各个入口,每个常量池入口的第一个字节都是个标志,用这个标志来表示该入口中存储的常量类型,如CONSTANT_Long表示里面存储的是long类型字面值,CONSTANT_Class_info表示里面存的是某个Class的类型信息(存的可能是个普通的字符串,然后经过常量池解析,则变成指向某个类的引用)。
除了字面常量值以外,常量池还可以容纳其它几种符号引用:类和接口的全限定名、字段名称和描述符、方法名称和描述符。
类和接口的全限定名指的是当前类的全限定名。
字段名称指的是类或接口的实例变量或类变量,字段的描述符是一个指示字段的类型的字符串。如在一个类中有以下形式的声明:A a = null, 则a为字段名,A为字段描述符。
方法的描述符也是个字符串,该字符串指示了方法的返回值和参数的数量、顺序和类型。
在运行时,JVM从常量池中获得符号引用,然后在运行时解析成引用项的实际地址,最后通过常量池中的全限定名、方法和字段描述符,把当前类或接口中的代码与其它类或接口中的代码联系起来。
讲到常量池,就涉及到常量池解析,常量池解析是非常复杂的,它分多种情况处理。这里,就列举个简单的例子,大家看看,大概了解下常量池解析的大概过程。
测试例子程序:
- /**
- * Dog的超类是个接口,而不是类
- */
- public class Animal {
- String id = "Animal"; //当类变量是static final
- public void print() {
- System.out.println("this is animal"+id);
- }
- }
- /**
- * 测试类
- */
- public class Test {
- public static void main(String[] args) {
- Animal a = new Dog();
- }
- }
下面将描述下JVM是如何处理Test类中的main()方法的第一条指令:
要运行Test程序,JVM找到对应的Test.class文件,并读入到方法区中。通过保存在方法区中的字节码,JVM开始执行main方法,执行时,它会一直指向当前类(Test类)的常量池。 main的第一条指令告诉JVM要为常量池中的第一项类分配足够的内存,于是JVM使用Test常量池指针找到第一项,发现它是一个对Animal类的符号引用(也就是对字符串“Animal”的引用),于是JVM检查方法区,看Animal类是否被装载了。
如JVM发现还没装载过Animal类时,它开始查找并装载Animal.class文件。
最后,JVM将一个直接指向方法区Animal类数据的指针来代替常量池第一项(也就是那个字符串”Animal”),以后,则可以通过该指针来快速访问Animal类了,这个就称为常量池解析。
说了那么多,常量池解析在这里作的处理就是把常量池中的字符串"Animal"解析成一个对类Animal的引用。