java 中的常量池概念附实例
2017-10-10 11:28
330 查看
1. java中的11中常量类型,
类型号 1-12 没有2类型号常量池中类型均有如下通用表示方式.
cp_info
{
u1 tag;
u1 info[];
}
但是对于具体的某种类型,还可以进一步细化u1 info[].
我们知道, 整型(int,long),实型(float,double),utf8是基本类型,
其它是引用类型.
string: 包含一个utf8
class: 包含一个utf8
nametype: 包含2个utf8,一个name,一个type.
field: 字段,包含一个类,一个nametype
method: 方法,包含一个类,一个nametype
interfaceMethod: 接口方法,包含一个接口,一个nametype
通俗的理解,field 相当于变量, method 相当于函数, 接口方法就是接口函数.
1.1: 5种基本类型
utf8字符串, 整型,实型,长整型,双精度型,有c基础的还是容易理解的.CONSTANT_Utf8 1 UTF-8编码字符串
CONSTANT_Utf8_info
{
u1 tag; // 1
u2 length;
u1 bytes[length];
}
CONSTANT_Integer 3
CONSTANT_Integer_info
{
u1 tag; //3
u4 bytes; // big endian
}
CONSTANT_Float 4
CONSTANT_Float_info
{
u1 tag; //4
u4 bytes; //IEEE 754单精度浮点格式
}
CONSTANT_Long 5
CONSTANT_Long_info
{
u1 tag; //5
u4 high_bytes; // big endian
u4 low_bytes;
}
CONSTANT_Double 6
CONSTANT_Double_info
{
u1 tag; //6
u4 high_bytes; // IEEE 754双精度浮点格式
u4 low_bytes;
}
1.2: 6种引用类型.
所谓的引用类型,是指当代码访问了成员变量,或者调用了成员函数,那么就构成了对类的成员变量或者类的成员函数的引用, 那个这个变量及其类型或者这个成员函数及其类型要保留到常量池中. 通俗的理解为你访问过什么对象,就要把这个对象保存到常量池中.6种引用类型依次为:
类引用, 字符串引用,字段引用,方法引用,接口方法引用,名字和类型变量.
除了名字和类型变量我们容易理解, 就是说一个索引指向名字字符串,一个索引指向类型字符串
这些常量可能代表着一个类,一个字符串,一个成员变量,一个函数(成员函数或接口函数),
它们并不代表一个具体数值.
这些概念如何在常量池中表示,?
既然名字和类型这么重要, 那么可以先定义一个名字类型常量, 别的类型就好包含这个名字类型常量了.
深刻的理解还要看后续详细说明.
CONSTANT_Class 7
CONSTANT_Class_info
{
u1 tag; //7
u2 name_index;
}
就是定义一个类或接口的符号引用, 比较简单,容易理解. 就是一个类名.
CONSTANT_String 8
CONSTANT_String_info
{
u1 tag; //8
u2 string_index;
}
就是定义一个字符串引用, 比较简单,容易理解.
当你要访问一个字符串时, 常量池中就包含了一个这样的结构.
CONSTANT_Fieldref 9
CONSTANT_Fieldref_info
{
u1 tag; //9
u2 class_index;
u2 name_and_type_index;
}
当你要引用一个字段时,需要知道它属于那个类,它叫什么名字,是什么类型.
例如: 类A 有一个成员变量i
Class A {
int i;
};
当你要访问A.i时, 常量池中就包含了一个如上的结构常量.
CONSTANT_Methodref 10
CONSTANT_Methodref_info
{
u1 tag; //10
u2 class_index;
u2 name_and_type_index;
}
当你要调用一个类的方法时, 需要知道它属于哪个类, 方法名称叫什么,参数及返回类型是什么.
例如: 类A 有一个成员函数叫geti()
Class A {
int i;
int geti(){return i;}
};
当你要调用类A的geti()方法时, 常量池中就加入了一个
Methodref 常量
CONSTANT_InterfaceMethodref 11 对一个接口中声明的方法的符号引用
CONSTANT_InterfaceMethodref_info
{
u1 tag; //11
u2 class_index;
u2 name_and_type_index;
}
接口方法与类方法类似, 只是它的class_index是接口而不是类, 其它相同.
CONSTANT_NameAndType 12
CONSTANT_NameAndType_info
{
u1 tag; //12
u2 name_index;
u2 descriptor_index;
}
就是对名称和类型的描述, 包括字段名称和字段类型, 方法名称和方法类型
名称是你随意起得名字,是任意字符串, 类型则有一定的规则.
1.3 类型的表达方式
为了减少存储空间,类型并不用源码中所表达的类型来表示,而是采用一种简化的方式.基本数据类型和void类型对应的字符
byte B
char C
double D
float F
int I
long J
short S
boolean Z
void V
引用数据类型
“L” + 类型的全限定名 + “;”
所谓类型的全限定名就是用/分割各包名形成的路径.例如object引用为:
Ljava/lang/Object;
多维数组类型表示
若干个“[” + 数组中元素类型的对应字符串
字段的类型描述符
int i –>I
object o –>Ljava/lang/Object;
方法类型描述符比较复杂, 包括所有参数的类型列表和方法返回值。
它的格式是这样的:
(参数1类型 参数2类型 参数3类型 …)返回值类型
举例:
方法类型描述符 方法声明
()I int getSize()
()Ljava/lang/String; String toString()
([Ljava/lang/String;)V void main(String[] args)
()V void wait()
(JI)V void wait(long timeout, int nanos)
(ZILjava/lang/String;II)Z boolean regionMatches(boolean ignoreCase, int toOffset, String other, int ooffset, int len)
([BII)I int read(byte[] b, int off, int len )
()[[Ljava/lang/Object; Object[][] getObjectArray()
我们看到, 格式化的方法类型描述符比源码中的方法声明还是简化了不少!
类的构造方法的方法名使用字符串 表示, 而静态初始化方法的方法名使用字符串 表示, 这两个是特殊名称.
有了上面的基本概念,我们看一下hello.java 的常量池吧.
2. hello.java 常量池
2.1 hello.java 源码
$ cat hello.java public class hello{ public static void main(String[] args) { System.out.println("hello world\n"); } }
2.2 生成hello.class
$ javac hello.java2.3 导出hello.class
$ javap -v hello.class Classfile /home/hjj/workspace/javat/hello.class Last modified Oct 10, 2017; size 416 bytes MD5 checksum 494dd3b28933beec5ca8cb3e62198615 Compiled from "hello.java" public class hello SourceFile: "hello.java" minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // hello world\n #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // hello #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 hello.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 hello world\n #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 hello #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V { public hello(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello world\n 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 4: 0 line 5: 8 }
2.4 分析常量池
我们只分析一下28个常量池, 至于其它留待以后再分析!utf8 常量17个
#7 = Utf8 //特殊名称
#8 = Utf8 ()V //方法类型
#9 = Utf8 Code //—-
#10 = Utf8 LineNumberTable //—-
#11 = Utf8 main //执行程序的入口
#12 = Utf8 ([Ljava/lang/String;)V //方法类型
#13 = Utf8 SourceFile //—-
#14 = Utf8 hello.java //程序名称
#18 = Utf8 hello world\n //程序中字符串
#21 = Utf8 hello //类名
#22 = Utf8 java/lang/Object //对象全限定名
#23 = Utf8 java/lang/System //对象全限定名
#24 = Utf8 out //对象字段
#25 = Utf8 Ljava/io/PrintStream; //对象引用
#26 = Utf8 java/io/PrintStream //对象全限定名
#27 = Utf8 println //方法名称
#28 = Utf8 (Ljava/lang/String;)V //方法类型
我们发现, 有3个标识为//—-的似乎意义不大, 其它我们都标识了名称的用途.
大体上3类,一个自定义名称,一个类型. 一个其它类中定义的名称.
NameAndType 常量3个
#15 = NameAndType #7:#8 // “”:()V
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
由此也直接看到了3个是name, 3个是type,
细分还知道, 一个是特殊名称,一个是成员名称,一个是方法名称.
Class 常量4个
分别为hello类, Object类,System类, PrintStream 类
#5 = Class #21 // hello
#6 = Class #22 // java/lang/Object
#16 = Class #23 // java/lang/System
#19 = Class #26 // java/io/PrintStream
String 引用1个
#3 = String #18 // hello world\n
就是hello world, 客户要打印的字符串.
Fieldref 1个
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
是对System.out 的引用
Methodref 2个
#1 = Methodref #6.#15 // java/lang/Object.””:()V
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
对Object 初始化函数的调用
对PrintStream的println 函数的调用.
常量池项 17+3+4+1+1+2 = 28 个分析完毕.
总结: java class 文件是对源代码的以此以人阅读为目的向以虚拟机阅读为目的的信息转换, 其中常量池除了描述数值外,还描述了代码中用到的成员变量及类型,代码中用到的成员函数及类型
相关文章推荐
- java中注解的基本概念以及实例
- java常量池概念(转)
- RabbitMQ消息队列入门篇(环境配置+Java实例+基础概念)
- java易混淆概念之类变量、实例变量、局部变量
- Java中堆、栈、常量池等概念解析
- Java多线程进阶(一)常见多线程概念汇总及实例演示
- Java中堆、栈、常量池等概念解析
- Java TCP通信概念及实例
- java常量池概念
- Cucumber概念解析与Java入门实例 (上)
- Java TCP通信概念及实例
- java中常量池实例解析 4000
- Java中 堆 栈,常量池等概念解析(转载)
- Java常量池概念
- Java中 堆 栈,常量池等概念解析(转载)
- java中常量池的概念及存在的区域
- Java中堆、栈、常量池等概念解析
- RabbitMQ消息队列入门篇(环境配置+Java实例+基础概念)
- java常量池概念
- RabbitMQ消息队列入门篇(环境配置+Java实例+基础概念)