Java类从编译到执行的那些事

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执行模板的途径就有很多:

  1. 网络流加载
  2. 本地文件流加载
  3. 数据库字段记录流
  4. 动态代理内存生成等
  5. 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

猜你喜欢

转载自blog.csdn.net/f45056231p/article/details/82320520
今日推荐