HelloWorld的执行过程

在看完《深入了解Java虚拟机》对象创建和类加载之后,想要连贯的对一段代码的执行过程进行一个追踪,以下是目前的个人理解。

先是一段的简单的HelloWorld代码

public class HelloWorld {
    public static void main(String[] args) {
        String string = "HelloWorld";
        System.out.println(string);
    }
}

整个的代码执行过程可以分为三个阶段:

  • 代码编译
  • 类加载
  • 类执行

代码编译

代码的编译不是很了解,这里盗用网上一个博主的图片:
//TODO
编译工具为javac,编译的结果是HelloWorld.class, 接下来说下HelloWorld.class文件的结构和内容。
字节码截图如下:


字节码

以若干字节为单位,U4即表示4个字节的长度,依次类推。

  • 前U4是魔数,用以确定该class文件是否能被虚拟机接受,表示这是一个class文件。
  • 接下来U4是版本号,U3,U4表示次版本号,U5,U6表示主版本号,高版本可以兼容低版本,反之不行。0x00000033即表示51.

接下来是常量池信息:

  • 接下来U2,常量池计数器,0x22,即常量池内有34个常量,即标注部分。


    常量池
  • 常量池中每一个常量都是以一个U1的类型标志位开始的。我们可以借助工具javap来分析class文件中的信息。
Classfile /D:/IDEA_workspace/juc/target/classes/com/zhangcf/test/impl/HelloWorld.class
  Last modified 2018-8-31; size 576 bytes
  MD5 checksum 516c2b5a8758322677a3f5dec4ef0d15
  Compiled from "HelloWorld.java"
public class com.zhangcf.test.impl.HelloWorld
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // HelloWorld
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // com/zhangcf/test/impl/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/zhangcf/test/impl/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               HelloWorld
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               com/zhangcf/test/impl/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public com.zhangcf.test.impl.HelloWorld();
    descriptor: ()V
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/zhangcf/test/impl/HelloWorld;

  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: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String HelloWorld
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

以上可以分为两个部分,第一个不是表示class文件中各位置的信息,后面大括号内的内容表示类中的方法,这里显示了无参构造和main方法。

整个class文件的结构可以总结为以下的图示:

class文件结构

其中
字段表(field_info)表示类中各字段的信息,包括访问标识,字段索引,描述符索引和属性表。
方法表(method_info)表示类中的方法,包含访问标识,名称索引,描述符索引,参数数量,参数属性信息等属性表,与字段表不同的是,方法表中的属性表包含Code属性,表示方法的方法实体。
class文件的最后固定是属性表(attrbute_info),关于属性表:

  • 属性表不仅存在于class文件的最后,字段表,方法表和方法表中的Code属性中也存在属性表,也即是属性表中也是可以存在属性表。
  • 属性表的长度是不固定的,不同的属性,属性表的长度是不同的。

类加载

类加载简单来说就是把class文件加载到虚拟机中,先说下类加载器:

类加载器

Java中的类加载器可以说有四种,分别是启动类加载器(Bootstrap ClassLoader),扩展类加载器(Extension Classloader), 应用程序类加载器(Application ClassLoader)和自定义类加载器。

其中,第一种是JVM内置的,代码中是不能显式使用的,另外的三种类加载器都需要继承ClassLoader类,ClassLoader类中的方法如下:

ClassLoader类

启动类加载器用于加载{JAVA_HOME}/lib/rt.jar;
扩展类加载器主要用于加载{JAVA_HOME}/lib/ext下的jar包;
应用程序类加载器用于加载用户类路径下的class文件或jar包,为系统默认类加载器
自定义类加载器中一般只需要重写loadClass(...)方法,若要使用上一级类加载器,调用super.loadClass(...)。

双亲委派原则

在一个类的加载中,它会首先被上层的类加载器所加载,若未找到该类文件,则交还给下级类加载器加载。
双亲委派原则的好处:

  • 避免重复加载,一次基础类加载之后,下次再需要加载的时候,直接使用即可,无需重复加载
  • 安全因素,这样的话,java的核心api不会被随意替换,可以防止恶意替换。

怎么样加载与系统类同名的类(打破双亲委派原则)?
该类位于D:\com\zhangcf\ClassPart\内

package com.zhangcf.ClassPart;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class MyClassLoader extends ClassLoader{
    @Override
    protected Class<?> findClass(String name) {
        File file = new File("D:\\com\\zhangcf\\ClassPart\\" + name.substring(name.lastIndexOf(".") + 1) + ".class");
        URI uri = file.toURI();
        String myPath = "D:\\com\\zhangcf\\ClassPart\\" + name.substring(name.lastIndexOf(".") + 1) + ".class";
        byte[] bytes = null;
        Path path = null;
        try{
            path = Paths.get(uri);
            bytes = Files.readAllBytes(path);
        } catch (IOException e) {
            e.printStackTrace();
        }

        Class cl = defineClass(name,bytes,0,bytes.length);
        return cl;
    }

    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> cla = myClassLoader.findClass("com.zhangcf.ClassPart.String");
        try {
            Object object = cla.newInstance();
            Method method = cla.getMethod("print");
            method.invoke(object);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

类加载的过程

类加载有7步组成。分别是 加载验证准备解析初始化使用卸载

加载:把class文件加载到虚拟机中,可以是本地文件,也可以是网络文件,也可以是在运行时生成,如动态代理。并在内存中生成一个Class对象,HotSpot虚拟机是将该对象存在方法区内。
验证:确保class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机。分为四个验证步骤:文件格式验证,元数据验证,字节码验证,符号引用验证。
准备:为类变量分配内存,并设置初始零值。这些变量都将在方法区中进行内存分配。
解析:将常量池内的符号引用替换为直接引用的过程。
关于符号引用和直接引用:

符号引用就是字符串,这个字符串包含足够的信息,以供实际使用时可以找到相应的位置。你比如说某个方法的符号引用,如:“java/io/PrintStream.println:(Ljava/lang/String;)V”。里面有类的信息,方法名,方法参数等信息。当第一次运行时,要根据字符串的内容,到该类的方法表中搜索这个方法。运行一次之后,符号引用会被替换为直接引用,下次就不用搜索了。直接引用就是偏移量,通过偏移量虚拟机可以直接在该类的内存区域中找到方法字节码的起始位置。

初始化:类加载过程的最后一步,根据程序员主观计划去初始化类变量和其他资源。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容