Java类从编译到执行的那些事
2017年11月29日 22:31:14 阅读数:222 标签: java jvm class 更多
个人分类: JAVA基础和提高
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_35908652/article/details/78669033
编写一个Java类时,如果我们用的记事本,通常一些Java通用编写规则的错误在编译期间发现。如果我们用的Eclipse’Intellij等带有编写时检查的IDE会帮助我们减少编译的甚至运行时的错误。
Java是个一次编写,各大平台可以执行的语言模式。这是通过各大平台对应的JVM帮助屏蔽了一些底层区别。想要了解Java从Object.java编为Object.class字节码并在cpu\内存中执行就得了解JVM的一些规范和技术原理。
在开始之前假设我们先在Eclipse写了以下代码:
(代码用来验证初始化时机和顺序)
public class App {
public static final int staticFinalApp = 39;
public static Random randApp = new Random(90);
public static final int staticFinalRTApp = randApp.nextInt(500);
public static int staticApp = 65;
static {
System.out.println("App static parameters "+staticFinalApp);
System.out.println("App static parameters "+randApp);
System.out.println("App static parameters "+staticFinalRTApp);
System.out.println("App static parameters "+staticApp);
System.out.println("App static initializing");
}
{
System.out.println("App non static initializing{}");
}
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("App main run");
// System.out.println("Son"+Son.staticFinalSon); //常量 不会引起静态初始化
// System.out.println("Father"+Father.staticFinalFather); //常量 不会引起静态初始化
//
//
// System.out.println("Son"+Son.staticFinalFather);//继承常量 不会引起静态初始化
//
// System.out.println("Son"+Son.staticFinalRTSon); // 不是编译期常量 会引起静态初始化
// System.out.println("================================");
// Class claz = Son.class;// 导致累加载 不会引起静态初始化
// Class.forName("com.sanwenyu.init.Son");//导致累加载 引起静态初始化
// Class.forName("com.sanwenyu.init.Son",false, ClassLoader.getSystemClassLoader());// false 指的是 不进行初始化
// Father father = new Father();// 常量初始化---静态类变量顺序初始化---类属性域顺序初始化---构造方法
Son son = new Son();// 常量初始化(先父类在子类)---静态类变量顺序初始化(先父类再子类)---类属性域顺序初始化接着构造方法(先父类再子类)
son.lookRandom();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
public class Father {
public static final int staticFinalFather = 39;
public static Random randFather = new Random(90);
public static int staticFinalRTFather = randFather.nextInt(100);
public static int staticFather = 65;
public static House staticHouse = new House("staticHouse");
public static final House staticfinalHouse = new House("staticfinalHouse");
static {
System.out.println("Father static initializing{}");
System.out.println("Father static parameters "+staticFinalFather);
System.out.println("Father static parameters "+randFather);
System.out.println("Father static parameters "+staticFinalRTFather);
System.out.println("Father static parameters "+staticFather);
}
public int age;
public int eyeType = 15;
public House house = new House(1);
{
System.out.println("Father non static initializing{}");
}
public Father() {
System.out.println("Father constructor");
new House(2);
}
public int lookRandom(){
int i =0;
try{
System.out.println("try"+i);
i= 9/0 ;
return i;
}catch(Exception e){
i= 8;
System.out.println("Exception"+i);
return i;
}finally{
i= 3;
System.out.println("finally"+i);
}
}
public House house3 = new House(3);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
public class Son extends Father {
public static final int staticFinalSon = 30;
public static Random randSon = new Random(90);
public static final int staticFinalRTSon = randFather.nextInt(600);
public static int staticSon = 36;
static {
System.out.println("Son static initializing" + staticFinalRTSon);
}
{
System.out.println("Son non static initializing{}");
}
public String name = "Son Name";
public Toys toy1 = new Toys(1);
public Son() {
System.out.println("Son constructor");
}
public Son(int t) {
System.out.println("Son constructor" + t);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
public class Toys {
public Toys() {
System.out.println("Son,s Toys constructor");
}
public Toys(int i) {
System.out.println("Son,s Toys constructor" + i);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
public class House {
public House(int i) {
System.out.println("Father House"+i);
}
public House(String string) {
System.out.println(string);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
1 从Object.java到Object.class
写好上面的代码后,Eclipse自动帮我们在Bin目录下生成了class文件。
Class文件的结构:
(下面的陈述请配合这节的图看)
魔数是为了标志这个是个java的class格式文件。比如我们用的图片文件、视频文件都有相应的魔数来标识。魔数的好处就是即使我们更改了文件后缀,相应的处理器也能识别出来。如果我们能读出字节流开始为魔数 CA FE BA BE。就可以确定这个文件是class类型的文件。
主版本号和次版本号 用来标明这个class是在哪些jre中可以加载执行的。如果jre的jvm发现版本号超过自己,将拒绝加载。
class里面的常量池会在加载到内存后变为运行时常量池,这个池子到底放到堆中参与GC还是方法区中参与GC。JVM规范并没有定死。java7 在堆中开辟了一块空间放运行时常量池。java6就是在方法区内。java8有待研究。
常量池存放了class的一些符号引用,比如方法名字、字段名字和类接口的全限定名。
也存放了诸如:
final static i = 386;
final static String = "我是class";
*对于String类型,可以在运行时通过intern方法将字符串加入运行时常量池。
- 1
- 2
- 3
类索引、父类索引将在class加载到方法区生成Class对象时进行实际地址解析。接口索引将在用到接口才进行Class对象地址解析。
字段仅对应java.class自己的的字段,没有父类或者接口的字段。(class 严格对应之前的java )
这里有个Volatile,专门进行了讨论:传送门。
- 1
方法包括 描述标志 、PC计数器所用的字节码属性:Code、异常表、本地变量表等。
*桥接方法标志 bridge .是和泛型机制有关的标志 。对它的描述请看 传送门。
*下面Son 实现了Boys<String> 的接口 goToSchool()在javap处理后会出现两个方法
goToSchool(String)
goToSchool(Object)编译器自动生成的桥接方法
- 1
- 2
- 3
- 4
- 5
属性表描述的属性是穿插在 类索引等 字段表 方法表之中的,用来描述更多的属性特征。方法的一些属性描述了方法在入方法栈的栈帧时的处理动作和预期。
@注解名
这里我们重点讨论下和编程息息相关的注解。传送门。
- 1
- 2
这里我直接将class格式总结了一张直观图:
笔者将Boys改为Boys [泛型String],让Son实现这个接口 ,并为Son加了个有同步控制的方法getSonFuture();
我们利用Java的工具 来看一看Son的字节码 :javap -verbose Son.class
Classfile /F:/Code/workspacetele/JavaApi/bin/com/sanwenyu/init/Son.class
Last modified 2017-11-28; size 1754 bytes
MD5 checksum 83b6c39a7878ed2bd052bec7d667676b
Compiled from "Son.java"
public class com.sanwenyu.init.Son extends com.sanwenyu.init.Father implements com.sanwenyu.init.Boy
s<java.lang.String>
SourceFile: "Son.java"
Signature: #111 // Lcom/sanwenyu/init/Father;Lcom/sanwenyu/init/Boys<Ljava
/lang/String;>;
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool: //静态常量池 可以看到 这部分占了相当大一部分
#1 = Class #2 // com/sanwenyu/init/Son
#2 = Utf8 com/sanwenyu/init/Son
#3 = Class #4 // com/sanwenyu/init/Father
#4 = Utf8 com/sanwenyu/init/Father
#5 = Class #6 // com/sanwenyu/init/Boys
#6 = Utf8 com/sanwenyu/init/Boys
#7 = Utf8 staticFinalSon
#8 = Utf8 I
#9 = Utf8 ConstantValue
#10 = Integer 30 //final static 类型的值被编译到这里
#11 = Utf8 randSon
#12 = Utf8 Ljava/util/Random;
#13 = Utf8 staticFinalRTSon
#14 = Utf8 staticSon
#15 = Utf8 ibj
#16 = Utf8 Ljava/lang/Object;
#17 = Utf8 name
#18 = Utf8 Ljava/lang/String;
#19 = Utf8 toy1
#20 = Utf8 Lcom/sanwenyu/init/Toys;
#21 = Utf8 oo
#22 = Integer 368
#23 = Utf8 <clinit>
#24 = Utf8 ()V
#25 = Utf8 Code
#26 = Class #27 // java/util/Random
#27 = Utf8 java/util/Random
#28 = Long 90l
#30 = Methodref #26.#31 // java/util/Random."<init>":(J)V
#31 = NameAndType #32:#33 // "<init>":(J)V
#32 = Utf8 <init>
#33 = Utf8 (J)V
#34 = Fieldref #1.#35 // com/sanwenyu/init/Son.randSon:Ljava/util/Random;
#35 = NameAndType #11:#12 // randSon:Ljava/util/Random;
#36 = Fieldref #1.#37 // com/sanwenyu/init/Son.randFather:Ljava/util/Random;
#37 = NameAndType #38:#12 // randFather:Ljava/util/Random;
#38 = Utf8 randFather
#39 = Methodref #26.#40 // java/util/Random.nextInt:(I)I
#40 = NameAndType #41:#42 // nextInt:(I)I
#41 = Utf8 nextInt
#42 = Utf8 (I)I
#43 = Fieldref #1.#44 // com/sanwenyu/init/Son.staticFinalRTSon:I
#44 = NameAndType #13:#8 // staticFinalRTSon:I
#45 = Fieldref #1.#46 // com/sanwenyu/init/Son.staticSon:I
#46 = NameAndType #14:#8 // staticSon:I
#47 = Fieldref #48.#50 // java/lang/System.out:Ljava/io/PrintStream;
#48 = Class #49 // java/lang/System
#49 = Utf8 java/lang/System
#50 = NameAndType #51:#52 // out:Ljava/io/PrintStream;
#51 = Utf8 out
#52 = Utf8 Ljava/io/PrintStream;
#53 = Class #54 // java/lang/StringBuilder
#54 = Utf8 java/lang/StringBuilder
#55 = String #56 // Son static initializing
#56 = Utf8 Son static initializing
#57 = Methodref #53.#58 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
#58 = NameAndType #32:#59 // "<init>":(Ljava/lang/String;)V
#59 = Utf8 (Ljava/lang/String;)V
#60 = Methodref #53.#61 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBui
er;
#61 = NameAndType #62:#63 // append:(I)Ljava/lang/StringBuilder;
#62 = Utf8 append
#63 = Utf8 (I)Ljava/lang/StringBuilder;
#64 = Methodref #53.#65 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#65 = NameAndType #66:#67 // toString:()Ljava/lang/String;
#66 = Utf8 toString
#67 = Utf8 ()Ljava/lang/String;
#68 = Methodref #69.#71 // java/io/PrintStream.println:(Ljava/lang/String;)V
#69 = Class #70 // java/io/PrintStream
#70 = Utf8 java/io/PrintStream
#71 = NameAndType #72:#59 // println:(Ljava/lang/String;)V
#72 = Utf8 println
#73 = Utf8 LineNumberTable
#74 = Utf8 LocalVariableTable
#75 = Methodref #3.#76 // com/sanwenyu/init/Father."<init>":()V
#76 = NameAndType #32:#24 // "<init>":()V
#77 = Class #78 // java/lang/Object
#78 = Utf8 java/lang/Object
#79 = Methodref #77.#76 // java/lang/Object."<init>":()V
#80 = Fieldref #1.#81 // com/sanwenyu/init/Son.ibj:Ljava/lang/Object;
#81 = NameAndType #15:#16 // ibj:Ljava/lang/Object;
#82 = String #83 // Son Name
#83 = Utf8 Son Name // 字符串被编译到这里
#84 = Fieldref #1.#85 // com/sanwenyu/init/Son.name:Ljava/lang/String;
#85 = NameAndType #17:#18 // name:Ljava/lang/String;
#86 = String #87 // Son non static initializing{}
#87 = Utf8 Son non static initializing{}
#88 = Methodref #53.#89 // java/lang/StringBuilder.append:(Ljava/lang/String;)Lja
/lang/StringBuilder;
#89 = NameAndType #62:#90 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#90 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#91 = Class #92 // com/sanwenyu/init/Toys
#92 = Utf8 com/sanwenyu/init/Toys
#93 = Methodref #91.#94 // com/sanwenyu/init/Toys."<init>":(I)V
#94 = NameAndType #32:#95 // "<init>":(I)V
#95 = Utf8 (I)V
#96 = Fieldref #1.#97 // com/sanwenyu/init/Son.toy1:Lcom/sanwenyu/init/Toys;
#97 = NameAndType #19:#20 // toy1:Lcom/sanwenyu/init/Toys;
#98 = Fieldref #1.#99 // com/sanwenyu/init/Son.oo:I
#99 = NameAndType #21:#8 // oo:I
#100 = Utf8 this
#101 = Utf8 Lcom/sanwenyu/init/Son;
#102 = Utf8 t
#103 = Utf8 getSonFuture
#104 = String #105 // nihao
#105 = Utf8 nihao
#106 = Methodref #1.#107 // com/sanwenyu/init/Son.goToSchool:(Ljava/lang/String;)V
#107 = NameAndType #108:#59 // goToSchool:(Ljava/lang/String;)V
#108 = Utf8 goToSchool
#109 = Methodref #110.#112 // com/sanwenyu/init/App.getAppCall:()V
#110 = Class #111 // com/sanwenyu/init/App
#111 = Utf8 com/sanwenyu/init/App
#112 = NameAndType #113:#24 // getAppCall:()V
#113 = Utf8 getAppCall
#114 = Methodref #1.#115 // com/sanwenyu/init/Son.lookRandom:()I
#115 = NameAndType #116:#117 // lookRandom:()I
#116 = Utf8 lookRandom
#117 = Utf8 ()I
#118 = Utf8 (Ljava/lang/Object;)V
#119 = Class #120 // java/lang/String
#120 = Utf8 java/lang/String
#121 = Utf8 SourceFile
#122 = Utf8 Son.java
#123 = Utf8 Signature
#124 = Utf8 Lcom/sanwenyu/init/Father;Lcom/sanwenyu/init/Boys<Ljava/lang/String;>;
public static final int staticFinalSon;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 30
public static java.util.Random randSon;
flags: ACC_PUBLIC, ACC_STATIC
public static final int staticFinalRTSon;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static int staticSon;
flags: ACC_PUBLIC, ACC_STATIC
public java.lang.Object ibj;
flags: ACC_PUBLIC
public java.lang.String name;
flags: ACC_PUBLIC
public com.sanwenyu.init.Toys toy1;
flags: ACC_PUBLIC
public final int oo;
flags: ACC_PUBLIC, ACC_FINAL
ConstantValue: int 368
static {}; //静态初始化块
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #26 // class java/util/Random
3: dup
4: ldc2_w #28 // long 90l
7: invokespecial #30 // Method java/util/Random."<init>":(J)V
10: putstatic #34 // Field randSon:Ljava/util/Random;
13: getstatic #36 // Field randFather:Ljava/util/Random;
16: sipush 600
19: invokevirtual #39 // Method java/util/Random.nextInt:(I)I
22: putstatic #43 // Field staticFinalRTSon:I
25: bipush 36
27: putstatic #45 // Field staticSon:I
30: getstatic #47 // Field java/lang/System.out:Ljava/io/PrintStream;
33: new #53 // class java/lang/StringBuilder
36: dup
37: ldc #55 // String Son static initializing
39: invokespecial #57 // Method java/lang/StringBuilder."<init>":(Ljava/lang
tring;)V
42: getstatic #43 // Field staticFinalRTSon:I
45: invokevirtual #60 // Method java/lang/StringBuilder.append:(I)Ljava/lang
tringBuilder;
48: invokevirtual #64 // Method java/lang/StringBuilder.toString:()Ljava/lan
String;
51: invokevirtual #68 // Method java/io/PrintStream.println:(Ljava/lang/Stri
;)V
54: return
LineNumberTable:
line 9: 0
line 11: 13
line 12: 25
line 17: 30
line 18: 54
LocalVariableTable:
Start Length Slot Name Signature
public com.sanwenyu.init.Son(); //<init> 构造器 可以看出 先初始化 父类实例 再顺序初始化自己的实例 然后执行构造器自己写的初始化
flags: ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
0: aload_0
1: invokespecial #75 // Method com/sanwenyu/init/Father."<init>":()V
4: aload_0
5: new #77 // class java/lang/Object
8: dup
9: invokespecial #79 // Method java/lang/Object."<init>":()V
12: putfield #80 // Field ibj:Ljava/lang/Object;
15: aload_0
16: ldc #82 // String Son Name
18: putfield #84 // Field name:Ljava/lang/String;
21: getstatic #47 // Field java/lang/System.out:Ljava/io/PrintStream;
24: new #53 // class java/lang/StringBuilder
27: dup
28: ldc #86 // String Son non static initializing{}
30: invokespecial #57 // Method java/lang/StringBuilder."<init>":(Ljava/lang
tring;)V
33: aload_0
34: getfield #84 // Field name:Ljava/lang/String;
37: invokevirtual #88 // Method java/lang/StringBuilder.append:(Ljava/lang/S
ing;)Ljava/lang/StringBuilder;
40: invokevirtual #64 // Method java/lang/StringBuilder.toString:()Ljava/lan
String;
43: invokevirtual #68 // Method java/io/PrintStream.println:(Ljava/lang/Stri
;)V
46: aload_0
47: new #91 // class com/sanwenyu/init/Toys
50: dup
51: iconst_1
52: invokespecial #93 // Method com/sanwenyu/init/Toys."<init>":(I)V
55: putfield #96 // Field toy1:Lcom/sanwenyu/init/Toys;
58: aload_0
59: sipush 368
62: putfield #98 // Field oo:I
65: return
LineNumberTable:
line 25: 0
line 13: 4
line 14: 15
line 20: 21
line 23: 46
line 24: 58
line 27: 65
LocalVariableTable:
Start Length Slot Name Signature
0 66 0 this Lcom/sanwenyu/init/Son;
public com.sanwenyu.init.Son(int);
flags: ACC_PUBLIC
Code:
stack=4, locals=2, args_size=2
0: aload_0
1: invokespecial #75 // Method com/sanwenyu/init/Father."<init>":()V
4: aload_0
5: new #77 // class java/lang/Object
8: dup
9: invokespecial #79 // Method java/lang/Object."<init>":()V
12: putfield #80 // Field ibj:Ljava/lang/Object;
15: aload_0
16: ldc #82 // String Son Name
18: putfield #84 // Field name:Ljava/lang/String;
21: getstatic #47 // Field java/lang/System.out:Ljava/io/PrintStream;
24: new #53 // class java/lang/StringBuilder
27: dup
28: ldc #86 // String Son non static initializing{}
30: invokespecial #57 // Method java/lang/StringBuilder."<init>":(Ljava/lang
tring;)V
33: aload_0
34: getfield #84 // Field name:Ljava/lang/String;
37: invokevirtual #88 // Method java/lang/StringBuilder.append:(Ljava/lang/S
ing;)Ljava/lang/StringBuilder;
40: invokevirtual #64 // Method java/lang/StringBuilder.toString:()Ljava/lan
String;
43: invokevirtual #68 // Method java/io/PrintStream.println:(Ljava/lang/Stri
;)V
46: aload_0
47: new #91 // class com/sanwenyu/init/Toys
50: dup
51: iconst_1
52: invokespecial #93 // Method com/sanwenyu/init/Toys."<init>":(I)V
55: putfield #96 // Field toy1:Lcom/sanwenyu/init/Toys;
58: aload_0
59: sipush 368
62: putfield #98 // Field oo:I
65: return
LineNumberTable:
line 29: 0
line 13: 4
line 14: 15
line 20: 21
line 23: 46
line 24: 58
line 31: 65
LocalVariableTable:
Start Length Slot Name Signature
0 66 0 this Lcom/sanwenyu/init/Son;
0 66 1 t I
public void getSonFuture();
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: getfield #80 // Field ibj:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter //同步进入标志
7: aload_0
8: ldc #104 // String nihao
10: putfield #84 // Field name:Ljava/lang/String;
13: aload_0
14: aload_0
15: getfield #84 // Field name:Ljava/lang/String;
18: invokevirtual #106 // Method goToSchool:(Ljava/lang/String;)V
21: aload_1
22: monitorexit //同步退出标志
23: goto 29
26: aload_1
27: monitorexit //如果同步代码块有异常则保证退出 这个是编译器自动加的
28: athrow
29: return
Exception table:
from to target type
7 23 26 any
26 28 26 any
LineNumberTable:
line 33: 0
line 34: 7
line 35: 13
line 33: 21
line 37: 29
LocalVariableTable:
Start Length Slot Name Signature
0 30 0 this Lcom/sanwenyu/init/Son;
public void goToSchool(java.lang.String);
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: invokestatic #109 // Method com/sanwenyu/init/App.getAppCall:()V
3: aload_0
4: invokevirtual #114 // Method lookRandom:()I 调用虚方法 多态虚方法 是运行时动态绑定
7: pop
8: return
LineNumberTable:
line 43: 0
line 44: 3
line 45: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/sanwenyu/init/Son;
0 9 1 t Ljava/lang/String;
public void goToSchool(java.lang.Object); // 桥接方法 内部先判断类型 再调用goToSchool:(String)
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #119 // class java/lang/String
5: invokevirtual #106 // Method goToSchool:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
我们再javap一个接口class看看:
接口
interface Boys{
int i =2;
String j= "你好";
void goToSchool();
}
Classfile /F:/Code/workspacetele/JavaApi/bin/com/sanwenyu/init/Boys.class
Last modified 2017-11-28; size 330 bytes
MD5 checksum 1087f634bd06596e8c98f23427f25c60
Compiled from "Boys.java"
interface com.sanwenyu.init.Boys<T extends java.lang.Object>
SourceFile: "Boys.java"
Signature: #19 // <T:Ljava/lang/Object;>Ljava/lang/Object;
minor version: 0
major version: 49
flags: ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
#1 = Class #2 // com/sanwenyu/init/Boys
#2 = Utf8 com/sanwenyu/init/Boys
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 i
#6 = Utf8 I
#7 = Utf8 ConstantValue
#8 = Integer 2
#9 = Utf8 j
#10 = Utf8 Ljava/lang/String;
#11 = String #12 // 你好
#12 = Utf8 你好
#13 = Utf8 goToSchool
#14 = Utf8 (Ljava/lang/Object;)V
#15 = Utf8 Signature
#16 = Utf8 (TT;)V
#17 = Utf8 SourceFile
#18 = Utf8 Boys.java
#19 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object;
{
public static final int i;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 2
public static final java.lang.String j; //static final
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String 你好
public abstract void goToSchool(T); //虚方法 实现者必须 定义
flags: ACC_PUBLIC, ACC_ABSTRACT
Signature: #16 // (TT;)V
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
这一步通过Javac来完成格式化的字节码创建。
class本身是个字节码流的文件,所以获得这个JVM执行模板的途径就有很多:
- 网络流加载
- 本地文件流加载
- 数据库字段记录流
- 动态代理内存生成等
- jar内加载
2 字节码class的加载
对于class的加载是通过加载器来完成的。同一个加载器环境下的class仅加载一次。不同加载器在同一个JVM内加载的同一个class在方法区生成的Class对应类不相同。
所以看两个实例类是否相同首先要确认是不是同一个加载器加载的Class类型类。
JAVA 允许我们开发自己的类加载器。但是鉴于上述类一致性论述。如果我们在一个jvm 上保留了很多类加载器。那么不能保证相同类具有唯一性。比如不能保证Java所有的类都继承自统一一个 java.lang.Object。
为了给类一个加载优先层次(用java提供的核心类或者扩展类交给java默认加载器加载,我们自己的加载器加载我们自己的类),JAVA的加载使用双亲委派模型。模型实现凸显的逻辑模板参照一下代码:
protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
//check the class has been loaded or not
Class c = findLoadedClass(name);//检查全限定名的类是否已经被加载到方法区
if(c == null){
try{
if(parent != null){
c = parent.loadClass(name,false);//委派给父加载器加载
}else{
c = findBootstrapClassOrNull(name);//委派给启动加载器加载
}
}catch(ClassNotFoundException e){
//if throws the exception ,the father can not complete the load
}
if(c == null){
c = findClass(name);//委派未成功 那么我自己加载处理吧
}
}
if(resolve){
resolveClass(c);
}
return c;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
class 的加载0要经过:加载1—>验证—>准备—>解析—>初始化—>使用—>卸载。
其中解析阶段可能穿插在运行时,来帮助方法动态调用的直接地址解析。
加载0的结果是
- 在方法区内创建一个类(接口、枚举)对应的的Class对象。
一般通过诸下获得方法区Class对象
Object.class
obj.getClass()
Class.ForName()
Class能干啥 请看java.lang.Class定义的接口。
- 1
- 2
- 3
- 4
- 5
- 在堆或者方法区创建一个 运行时常量池。
- 解析出常量池中 父类 接口 私有方法 静态方法 重载方法等直接地址
-
方法区开辟类属性内存并赋值
验证阶段要验证很多问题(语法、名字等 其他没太关系)。如果一个程序已经部署好了,说明已经验证过了。重启时不进行验证也好。通过JVM参数 :-Xverify:none关闭。
准备阶段 分配方法区属性内存 并赋默认值(0 false null)。这时候final static 的如果有ConstantValue就直接赋值了。
解析阶段 为常量池中的 简单名和全限定名 指定直接地址。重载使用静态分派,(private \static\构造器)直接指定地址
初始化阶段 (更详细的步骤看总结的直观图):
静态类字段初始化(先父后子)
实例字段初始化(类内根据声明顺序,先初始化父类 在初始化子类)
- 1
- 2
引起以上步骤动作有:
new newarray
getstatic putstatic
invokestatic
反射调用
父类初始化
- 1
- 2
- 3
- 4
- 5
加载过程总结图:
3 JVM执行Code字节码和堆内创建实例
JVM想要执行方法必然需要方法调用栈。
还需要控制方法Code 字节指令码按行号执行的PC计数器。
JVM内可以运行多个线程,每个线程都有自己的方法栈。这些线程共享堆内存对象和方法区Class对象和运行时常量池。
如果一个线程要在共享堆内存创建对象需要对堆加锁。
- 1
- 2
- 3
方法栈内有很多方法对应的栈帧。
栈帧包括:
- 局部变量表
在编译期就已经定了哪些变量
非静态方法局部变量表第一个Slot曹内的值是this引用(指向对象的地址)
GC Roots 的一部分计算表
局部变量用过之后 在后续code指令中不在用的话可以重复利用它占的曹
- 1
- 2
- 3
- 4
- 操作数栈
指令控制的数值栈
- 1
- 动态链接
需要运行时(对于栈帧来说是invokeVirtrueh或者invokeDynamic的此时)动态指定的方法地址
多态先根据实际引用类型地址来查找——>再父
- 1
- 2
- 返回地址
- 栈上对象
新建对象有可能要 做逃逸分析(看对象作用域能在多大范围 )
逃不出方法域的可以在栈帧内创建(小对象)
- 1
- 2
我们再来看jvm的内存模型:JVM 规范并没有规定死 内存的开辟方式和位置。完全可以自己根据JVM实现语言来约定。
栈上方法执行创建对象时的动作:
1 逃逸分析判断在栈上分配还是堆上分配 未逃逸 分配在栈帧内存空闲区
2 是否开启线程私有缓冲区 是的话 进行TLAB_Top+size<=TLAB_end判断 直接在TLAB上分配
否则申请TLAB
3 2放不下 则在Eden区加锁 Eden放不下 进行Young GC
4 3放不下 直接放到老年代 (很有可能是大对象了)
5 大对象、长期存活对象直接放老年代
- 1
- 2
- 3
- 4
- 5
- 6
- 7
PC计数器计算行号获得Code方法调用命令,然后:
A InvokeVirtual指令,调用虚方法,主要针对父继承方法、重载方法、覆写的方法。
- InvokeVirtual指令调用1:
【覆写:对于动态链接的方法 】
【父类继承的方法 接口实现的方法】
1 操作数栈顶第一个元素指向对象的实际类型 C(即使是装饰类型 :父类 接口 指引的地址)
2 C中找方法 有则返回直接地址 无则按照继承关系从下往上 依次对C的各个父类进行查找该方法
3 依然无 抛出AbstractMethodError
- InvokeVirtual指令调用2:
【重载】
编译阶段就已经获得方法直接地址
重载方法是个依赖静态类型(和实际类型相对) 执行方法版本的静态分派过程
静态类型 又叫外观类型 和实际类型有区别
编译阶段引用正确与否是根据装饰类型来判断的。
比如:
- 1
- 2
Father f = new Son();
f.goToSchool();//编写时 f是静态类 f内没有这个方法 此方法是子类扩展的
((Son)f).goToSchool();//要转化成 goToShool()所在的静态类
*运行时如果打印地址的话 其实上面两个调用方法地址一样。说明 Java程序规范和java Jvm实现规范需要分开对待
- 1
- 2
- 3
- 4
- 5
B invokestatic 指令,调用类方法。
C invokespecial指令,调用构造方法 私有方法
在调用非静态方法时 ,创建的栈帧中局部变量表第一个Slot是This(实例类地址)
其实在继承结构的运行时,super和this的地址是一样的,都指向子类实例地址。
我们来具体看一下例子的输出:(继承机构中 super this 和装饰类型 实际类型 都是约束你编程的,告诉你OOP逻辑上的正确性。运行时的具体地址情况则是JVM实现的原理。)
public Father() {
System.out.println("Father constructor"+this);
System.out.println("Father constructor"+super.toString());
}
public int lookRandom(){
System.out.println("lookRandom"+this);
}
===================================================================
public Son() {
System.out.println("Son constructor"+super.toString());
System.out.println("Son constructor"+this.toString());
}
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("App main run");
Father son = new Son();//1
System.out.println("Father"+son);
System.out.println("================================");
Son son1 = new Son();//2
System.out.println("Son"+son1);
}
打印:
Father constructorcom.sanwenyu.init.Son@4e25154f
Father constructorcom.sanwenyu.init.Son@4e25154f
Son constructorcom.sanwenyu.init.Son@4e25154f
Son constructorcom.sanwenyu.init.Son@4e25154f
lookRandomcom.sanwenyu.init.Son@4e25154f
Father com.sanwenyu.init.Son@4e25154f
================================
Father constructorcom.sanwenyu.init.Son@70dea4e
Father constructorcom.sanwenyu.init.Son@70dea4e
Son constructorcom.sanwenyu.init.Son@70dea4e
Son constructorcom.sanwenyu.init.Son@70dea4e
lookRandomcom.sanwenyu.init.Son@70dea4e
Son com.sanwenyu.init.Son@70dea4e
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
4 对象的内存结构:
我们将这个内容单独出来总结,有利于我们平时的编程。
对象方法中this 和 super的调用以及继承结构的静态类型和实际类型方法调用分析
完全转化为 编译期字面约束和运行时内存地址的分析。
这个思路方向有助于我们来分析对象内存结构。
方法调用时 局部变量表 之所以要传入 This地址是要 在方法 PC按行执行COde过程中 找到所属类的域属性并入操作数栈。
我们是用Jmap 和 Jhat指令来看下 Son运行时的内存状态:
可以看到内存实例最少包括:
父类继承属性和私有属性
自己的属性
属性值
属性引用对象地址(属性值)
*指向方法区对应Class类型的class对象
- 1
- 2
- 3
- 4
- 5
对于指向的Class对象:
最少包括:
父类Class地址
接口Class地址
这个类的类加载器地址
实例域
静态域
* 指向所有实例的地址集合(这个跟具体实现有关 可以没有)
- 1
- 2
- 3
- 4
- 5
- 6
到此刻我们其实应有个疑问:类私有域放到了子类内存结构当中了,为啥却访问不到呢?
是有标志使得其不可见吗?
经过上面编译期字面约束和运行时内存地址的分析思路,笔者认为:地址可以访问到,但编译期规范不让你访问到罢了。做了一个静态屏蔽罢了。
- 1
根据JVM规范:
实例对象应有的结构为:
1 对象头
2 指向Class类型的指针
[数组对象 长度]
3 属性域
4 对齐填充
- 1
- 2
- 3
- 4
- 5
对象头中包含(以32位HotSpot为例):
关于对象头中锁的机制和应用,请看 传送门。
5 对象回收
JAVA语言相较于C++有个特性,即帮助程序员来回收不在使用的对象。C++将回收对象内存的权限交给程序员,是因为程序员才真正知道他所创建的对象什么时候要用,什么时候不会再用了,不用了就可以手动回收对象的内存,将这部分内存交给系统管理为空闲内存。
而JAVA将这部分工作收为JVM来实现。即自动回收内存,以防止程序员疏忽导致的内存泄漏。
程序员很清楚什么时候对象可以被回收了,那么“机器”是如何知道对象应该被回收了呢?
JAVA程序创建的对象主要分布在堆上(实例区和方法区),少部分分布在栈内。如果在这些地方进行枚举对象引用并寻对象内部的引用链进行寻找,凡是在这引用链上的对象都标记为“可以枚举引用开始寻达的”,那么最后会将对象分为“可达的”和“不可达“。
上述搜索开端所使用的枚举引用叫做“GC Roots”:可达性分析的开端。
下图示意gc roots 和 可达性:
Stop The World:可达性分析需要保证分析时全局引用关系不在变化,所以需要取得虚拟机一致性的快照。就好像整个执行系统冻结在某个时间点上。
现在的虚拟机都是准确式GC ,以减少冻结时间。有办法直接得知哪些地方存放着对象引用(GC roots)。
- 1
- 2
- 3
对象被标识为不可达以后 如果发现其覆盖了finalize()方法并且没有被虚拟机调用过则将找个对象暂且放入F-Queue队列中 虚拟机创建低优先级Finalizer去执行队列对象的Finalize()方法。所以对象可以在这个方法内自救。然后GC会对对F-Q进行二次扫描,再决定回收与否。
预示着:GC中 如果类覆写了Finalize()方法,会在对象被回收时调用一次。
没有覆写的第一次标记后即被回收。
* 不建议覆写这个方法 会增加GC的时间
- 1
- 2
- 3
为了标志对象的重要程度和同内存的紧要关系。将可达的对象引用进行了进一步分类:
(通过引用标示来标示对象和GC之间的亲戚关系)
简单的软、弱、虚引用代码:
public static void main(String[] args) throws ClassNotFoundException,
InterruptedException {
System.out.println("App main run");
Toys toy1 = new Toys("SoftReference");
Toys toy2 = new Toys("WeakReference");
Toys toy3 = new Toys("PhantomReference");
SoftReference<Toys> sr = new SoftReference<Toys>(toy1);
WeakReference<Toys> wr = new WeakReference<Toys>(toy2);
final ReferenceQueue<Toys> req = new ReferenceQueue<Toys>();
new Thread() {
public void run() {
System.out.println("开始"+System.currentTimeMillis());
Object j = null;
try {
j = req.remove();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (j != null) {
System.out.println("虚引指" + j);
System.out.println(System.currentTimeMillis());
}
System.out.println("结束"+System.currentTimeMillis());
}
}.start();
PhantomReference<Toys> pr = new PhantomReference<Toys>(toy3, req);
System.out.println("虚引指:::" + pr);
toy1 = null;// sr 指向 内存还足 不被回收
toy2 = null;// wr 指向 遇到GC即被回收
toy3 = null;// pr 接收回收通知
Thread.sleep(2000);
System.out.println("开始GC");
System.gc();
Thread.sleep(2000);
if (wr.get() != null) {
System.out.println("wr 指向 还活着");
} else {
System.out.println("wr 指向 已被回收");
}
if (sr.get() != null) {
System.out.println("sr 指向 还活着");
} else {
System.out.println("sr 指向 已被回收");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
根据对象的生命周期将对象所在的堆内存分为:
Mark 一下 G1回收器。标记 标记 最终标记 回收。
6卸载
卸载或者说方法区内GC后Class类被回收。
方法区内Class类被回收条件:
该类的所有实例被回收。Java 堆中不存在该类的任何实例。
加载该类的类加载器ClassLoader被回收了
该类对应的Class对象没有又在任何地方被引用,无法在任何地方通过反射访问该类方法。
最后JVM 参数:
这位仁兄总结的不错:
http://blog.csdn.net/huaweitman/article/details/50552960