Java语言只是我们了解编程的基础语法,最终编译成的Class文件才是JVM解读的二进制文件,了解Class文件结构,有助于理解编程原理。通过javap我们将了解Class内部结构。
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据, 没有空隙存在。当遇到需要占用8位字节以上空间的数据项时, 则会按照高位在前[1]的方式分割成若干个8位字节进行存储。
根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表,后面的解析都要以这两种数据类型为基础。
无符号数属于基本的数据类型, 以u1、 u2、 u4、 u8来分别代表1个字节、 2个字节、 4个字节和8个字节的无符号数, 无符号数可以用来描述数字、 索引引用、 数量值或者按照UTF-8编码构成字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。
《Java虚拟机规范》对这些表结构有详细的定义,需要仔细研究。
Class文件格式结构
u4 magic; // 魔法数字,表明当前文件是.class文件,固定0xCAFEBABE
u2 minor_version; // 分别为Class文件的副版本和主版本
u2 major_version;
u2 constant_pool_count; // 常量池计数
cp_info constant_pool[constant_pool_count-1]; // 常量池内容
u2 access_flags; // 类访问标识
u2 this_class; // 当前类
u2 super_class; // 父类
u2 interfaces_count; // 实现的接口数
u2 interfaces[interfaces_count]; // 实现接口信息
u2 fields_count; // 字段数量
field_info fields[fields_count]; // 包含的字段信息
u2 methods_count; // 方法数量
method_info methods[methods_count]; // 包含的方法信息
u2 attributes_count; // 属性数量
attribute_info attributes[attributes_count]; // 各种属性
描述符
一个描述字段或方法的类型的字符串。
常量池
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。 而符号引用则属于编译原理方面的概念,包括了下面三类常量:
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
格式:
tag 表示常量项类型,整理如下:
常量池是最烦琐的数据, 是因为这14种常量类型各自均有自己的结构。
访问标志
在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。
类索引、 父类索引与接口索引集合
类索引( this_class) 和父类索引( super_class) 都是一个u2类型的数据,而接口索引集合( interfaces) 是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。
字段表集合
字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。另外,在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。
格式:
方法表集合
Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式,方法表的结构如同字段表一样,依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项。
方法的定义可以通过访问标志、 名称索引、 描述符索引表达清楚,但方法里面的代码去哪里了?方法里的Java代码, 经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里面,属性表作为Class文件格式中最具扩展性的一种数据项目。
如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。但同样的, 有可能会出现由编译器自动添加的方法, 最典型的便是类构造器“<clinit>”方法和实例构造器“< init>”方法。
在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名中,因此Java语言里面是无法仅仅依靠返回值的不同来对一个已有方法进行重载的。但是在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法也可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个Class文件中的。
格式:
属性表集合
与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松了一些,不再要求各个属性表具有严格顺序,并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。
格式:
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类、方法表、字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对用关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
StackMapTable | Code属性 | JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配 |
Signature | 类、方法表、字段表 | JDK1.5中新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Signature属性会为他记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦出后导致签名混乱,需要这个属性记录泛型中的相关信息 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | JDK 1.6中新增的属性,SourceDebugExtension属性用于存储额外的调试信息,譬如在进行JSP文件调试时,无法同构Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码并运行在Java虚拟机中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension属性就可以用于存储这个标准所新加入的调试信息 |
Synthetic | 类、方法表、字段表 | 标识方法或字段为编译器自动生成的 |
LocalVariableTypeTable | 类 | JDK 1.5中新增的属性,他使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 |
RuntimeVisibleAnnotations | 类、方法表、字段表 | JDK 1.5中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations属性用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的 |
RuntimeInVisibleAnnotations | 类、方法表、字段表 | JDK 1.5新增的属性,与RuntimeVisibleAnnotations属性作用刚好相反,用于指明哪些注解是运行时不可见的 |
RuntimeVisibleParameter Annotations | 方法表 | JDK 1.5新增的属性,作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法参数 |
RuntimeInVisibleAnnotations Annotations | 方法表 | JDK 1.5中新增的属性,作用与RuntimeInVisibleAnnotations属性类似,只不过作用对象为方法参数 |
AnnotationDefault | 方法表 | JDK 1.5中新增的属性,用于记录注解类元素的默认值 |
BootstrapMethods | 类文件 | JDK 1.7中新增的属性,用于保存invokedynamic指令引用的引导方法限定符 |
javap介绍
用法:
-help --help -? 输出此用法消息
-version 版本信息,其实是当前javap所在jdk的版本信息,不是class在哪个jdk下生成的。
-v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类 和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-classpath <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
$ javap -vTest
Classfile /C:/eclipse-workspace/mengying-xgj-english/ziguo-english-tools/target/classes/com/huaisu/generate/ftl/Test.class
Last modified 2019-11-26; size 1559 bytes
MD5 checksum 2a8f0d409a80b131f1caee022a18c8a4
Compiled from "Test.java"
public class com.huaisu.generate.ftl.Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // com/huaisu/generate/ftl/Test
#2 = Utf8 com/huaisu/generate/ftl/Test
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/huaisu/generate/ftl/Test;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Class #17 // com/huaisu/generate/ftl/Test$1
#17 = Utf8 com/huaisu/generate/ftl/Test$1
#18 = Methodref #16.#9 // com/huaisu/generate/ftl/Test$1."<init>":()V
#19 = Methodref #16.#20 // com/huaisu/generate/ftl/Test$1.start:()V
#20 = NameAndType #21:#6 // start:()V
#21 = Utf8 start
#22 = Class #23 // com/huaisu/generate/ftl/Test$2
#23 = Utf8 com/huaisu/generate/ftl/Test$2
#24 = Methodref #22.#9 // com/huaisu/generate/ftl/Test$2."<init>":()V
#25 = Methodref #22.#20 // com/huaisu/generate/ftl/Test$2.start:()V
#26 = Class #27 // com/huaisu/generate/ftl/Test$3
#27 = Utf8 com/huaisu/generate/ftl/Test$3
#28 = Methodref #26.#9 // com/huaisu/generate/ftl/Test$3."<init>":()V
#29 = Methodref #26.#20 // com/huaisu/generate/ftl/Test$3.start:()V
#30 = Utf8 args
#31 = Utf8 [Ljava/lang/String;
#32 = Utf8 send
#33 = Utf8 (Ljava/lang/String;)V
#34 = Methodref #35.#37 // java/lang/String.intern:()Ljava/lang/String;
#35 = Class #36 // java/lang/String
#36 = Utf8 java/lang/String
#37 = NameAndType #38:#39 // intern:()Ljava/lang/String;
#38 = Utf8 intern
#39 = Utf8 ()Ljava/lang/String;
#40 = Fieldref #41.#43 // java/lang/System.out:Ljava/io/PrintStream;
#41 = Class #42 // java/lang/System
#42 = Utf8 java/lang/System
#43 = NameAndType #44:#45 // out:Ljava/io/PrintStream;
#44 = Utf8 out
#45 = Utf8 Ljava/io/PrintStream;
#46 = Class #47 // java/lang/StringBuilder
#47 = Utf8 java/lang/StringBuilder
#48 = Methodref #49.#51 // java/lang/Thread.currentThread:()Ljava/lang/Thread;
#49 = Class #50 // java/lang/Thread
#50 = Utf8 java/lang/Thread
#51 = NameAndType #52:#53 // currentThread:()Ljava/lang/Thread;
#52 = Utf8 currentThread
#53 = Utf8 ()Ljava/lang/Thread;
#54 = Methodref #49.#55 // java/lang/Thread.getName:()Ljava/lang/String;
#55 = NameAndType #56:#39 // getName:()Ljava/lang/String;
#56 = Utf8 getName
#57 = Methodref #35.#58 // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
#58 = NameAndType #59:#60 // valueOf:(Ljava/lang/Object;)Ljava/lang/String;
#59 = Utf8 valueOf
#60 = Utf8 (Ljava/lang/Object;)Ljava/lang/String;
#61 = Methodref #46.#62 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
#62 = NameAndType #5:#33 // "<init>":(Ljava/lang/String;)V
#63 = String #64 // ,
#64 = Utf8 ,
#65 = Methodref #46.#66 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#66 = NameAndType #67:#68 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#67 = Utf8 append
#68 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#69 = Methodref #46.#70 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#70 = NameAndType #71:#39 // toString:()Ljava/lang/String;
#71 = Utf8 toString
#72 = Methodref #73.#75 // java/io/PrintStream.println:(Ljava/lang/String;)V
#73 = Class #74 // java/io/PrintStream
#74 = Utf8 java/io/PrintStream
#75 = NameAndType #76:#33 // println:(Ljava/lang/String;)V
#76 = Utf8 println
#77 = Long 5000l
#79 = Methodref #49.#80 // java/lang/Thread.sleep:(J)V
#80 = NameAndType #81:#82 // sleep:(J)V
#81 = Utf8 sleep
#82 = Utf8 (J)V
#83 = Methodref #84.#86 // java/lang/InterruptedException.printStackTrace:()V
#84 = Class #85 // java/lang/InterruptedException
#85 = Utf8 java/lang/InterruptedException
#86 = NameAndType #87:#6 // printStackTrace:()V
#87 = Utf8 printStackTrace
#88 = Utf8 name
#89 = Utf8 Ljava/lang/String;
#90 = Utf8 e
#91 = Utf8 Ljava/lang/InterruptedException;
#92 = Utf8 StackMapTable
#93 = Class #94 // java/lang/Throwable
#94 = Utf8 java/lang/Throwable
#95 = Utf8 SourceFile
#96 = Utf8 Test.java
#97 = Utf8 InnerClasses
{
public com.huaisu.generate.ftl.Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/huaisu/generate/ftl/Test;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: new #16 // class com/huaisu/generate/ftl/Test$1
3: dup
4: invokespecial #18 // Method com/huaisu/generate/ftl/Test$1."<init>":()V
7: invokevirtual #19 // Method com/huaisu/generate/ftl/Test$1.start:()V
10: new #22 // class com/huaisu/generate/ftl/Test$2
13: dup
14: invokespecial #24 // Method com/huaisu/generate/ftl/Test$2."<init>":()V
17: invokevirtual #25 // Method com/huaisu/generate/ftl/Test$2.start:()V
20: new #26 // class com/huaisu/generate/ftl/Test$3
23: dup
24: invokespecial #28 // Method com/huaisu/generate/ftl/Test$3."<init>":()V
27: invokevirtual #29 // Method com/huaisu/generate/ftl/Test$3.start:()V
30: return
LineNumberTable:
line 6: 0
line 10: 7
line 11: 10
line 15: 17
line 16: 20
line 20: 27
line 21: 30
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 args [Ljava/lang/String;
public static void send(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=1
0: aload_0
1: invokevirtual #34 // Method java/lang/String.intern:()Ljava/lang/String;
4: dup
5: astore_1
6: monitorenter
7: getstatic #40 // Field java/lang/System.out:Ljava/io/PrintStream;
10: new #46 // class java/lang/StringBuilder
13: dup
14: invokestatic #48 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
17: invokevirtual #54 // Method java/lang/Thread.getName:()Ljava/lang/String;
20: invokestatic #57 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
23: invokespecial #61 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
26: ldc #63 // String ,
28: invokevirtual #65 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: aload_0
32: invokevirtual #65 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #69 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: invokevirtual #72 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
41: ldc2_w #77 // long 5000l
44: invokestatic #79 // Method java/lang/Thread.sleep:(J)V
47: goto 55
50: astore_2
51: aload_2
52: invokevirtual #83 // Method java/lang/InterruptedException.printStackTrace:()V
55: aload_1
56: monitorexit
57: goto 63
60: aload_1
61: monitorexit
62: athrow
63: return
Exception table:
from to target type
41 47 50 Class java/lang/InterruptedException
7 57 60 any
60 62 60 any
LineNumberTable:
line 24: 0
line 25: 7
line 27: 41
line 28: 47
line 29: 51
line 24: 55
line 32: 63
LocalVariableTable:
Start Length Slot Name Signature
0 64 0 name Ljava/lang/String;
51 4 2 e Ljava/lang/InterruptedException;
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 50
locals = [ class java/lang/String, class java/lang/String ]
stack = [ class java/lang/InterruptedException ]
frame_type = 4 /* same */
frame_type = 68 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 2
}
SourceFile: "Test.java"
InnerClasses:
#16; //class com/huaisu/generate/ftl/Test$1
#22; //class com/huaisu/generate/ftl/Test$2
#26; //class com/huaisu/generate/ftl/Test$3
内部类字节码,理解内部类
public class InnerClassTest {
int field1 = 1;
private int field2 = 2;
public InnerClassTest() {
InnerClassA inner = new InnerClassA();
int v = inner.x2;
}
public class InnerClassA {
int x1 = field1;
private int x2 = field2;
}
public static void main(String[] args) {
InnerClassTest outerObj = new InnerClassTest();
// 不在外部类内部,使用:外部类对象. new 内部类构造器(); 的方式创建内部类对象
// InnerClassA innerObj = outerObj.new InnerClassA();
}
}
javap -c InnerClassTest.class
C:\java\jdk1.8.0\bin\javap -c InnerClassTest.class
Compiled from "InnerClassTest.java"
public class com.huaisu.generate.ftl.InnerClassTest {
int field1;
public com.huaisu.generate.ftl.InnerClassTest();
Code:
0: aload_0
1: invokespecial #11 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #13 // Field field1:I
9: aload_0
10: iconst_2
11: putfield #15 // Field field2:I
14: new #17 // class com/huaisu/generate/ftl/InnerClassTest$InnerClassA
17: dup
18: aload_0
19: invokespecial #19 // Method com/huaisu/generate/ftl/InnerClassTest$InnerClassA."<init>":(Lcom/huaisu/generate/ftl/InnerClassTest;)V
22: astore_1
23: aload_1
24: invokestatic #22 // Method com/huaisu/generate/ftl/InnerClassTest$InnerClassA.access$0:(Lcom/huaisu/generate/ftl/InnerClassTest$InnerClassA;)I
27: istore_2
28: return
public static void main(java.lang.String[]);
Code:
0: new #1 // class com/huaisu/generate/ftl/InnerClassTest
3: dup
4: invokespecial #35 // Method "<init>":()V
7: astore_1
8: return
static int access$0(com.huaisu.generate.ftl.InnerClassTest);
Code:
0: aload_0
1: getfield #15 // Field field2:I
4: ireturn
}
javap -c InnerClassTest$InnerClassA.class
Compiled from "InnerClassTest.java"
public class com.huaisu.generate.ftl.InnerClassTest$InnerClassA {
int x1;
final com.huaisu.generate.ftl.InnerClassTest this$0;
public com.huaisu.generate.ftl.InnerClassTest$InnerClassA(com.huaisu.generate.ftl.InnerClassTest);
Code:
0: aload_0
1: aload_1
2: putfield #13 // Field this$0:Lcom/huaisu/generate/ftl/InnerClassTest;
5: aload_0
6: invokespecial #15 // Method java/lang/Object."<init>":()V
9: aload_0
10: aload_1
11: getfield #18 // Field com/huaisu/generate/ftl/InnerClassTest.field1:I
14: putfield #23 // Field x1:I
17: aload_0
18: aload_1
19: invokestatic #25 // Method com/huaisu/generate/ftl/InnerClassTest.access$0:(Lcom/huaisu/generate/ftl/InnerClassTest;)I
22: putfield #29 // Field x2:I
25: return
static int access$0(com.huaisu.generate.ftl.InnerClassTest$InnerClassA);
Code:
0: aload_0
1: getfield #29 // Field x2:I
4: ireturn
}
编译器给内部类提供了一个接受 InnerClassTest
类型对象(即外部类对象)的构造方法,内部类本身还定义了一个名为 this$0
的 InnerClassTest
类型的引用,这个引用在构造方法中指向了参数所对应的外部类对象。
内部类的构造方法通过 invokestatic 指令执行外部类的 access$100 静态方法得到外部类对象的 field2 字段的值,并且在后面赋值给 x2 字段。这样的话内部类就成功的通过外部类提供的静态方法得到了对应外部类对象的 field2 。
在非静态内部类访问外部类私有成员 或 外部类访问内部类私有成员的时候,对应的外部类生成一个静态方法,用来返回对应私有成员的值,而对应外部类对象或内部类对象通过调用其内部类或外部类提供的静态方法来获取对应的私有成员的值。
静态内部类没有指向外部类对象的引用,编译器只为这个静态内部类提供了一个无参构造方法。
而且因为外部类对象需要访问当前类的私有成员,编译器给这个静态内部类生成了一个名为 access$000
的静态方法。
内存泄露
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
在Java语言中, 可作为GC Roots的对象包括下面几种:
- 虚拟机栈( 栈帧中的本地变量表) 中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI( 即一般说的Native方法) 引用的对象。
判定一个常量是否是“废弃常量”比较简单, 而要判定一个类是否是“无用的类”的条件则相对苛刻许多。 类需要同时满足下面3个条件才能算是“无用的类”:该类所有的实例都已经被回收, 也就是Java堆中不存在该类的任何实例。加载该类的ClassLoader已经被回收。该类对应的java.lang.Class对象没有在任何地方被引用, 无法在任何地方通过反射访问该类的方法。
在大量使用反射、 动态代理、 CGLib等ByteCode框架、 动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能, 以保证永久代不会溢出。
参考: