Java - 从.java文件到类加载

1 篇文章 0 订阅
1 篇文章 0 订阅

一、权威机构如何描述JDK

地址:https://docs.oracle.com/javase/8/docs/index.html

在图中可以看出Jdk,Jre,Jvm 三者的关系,Jdk包含了Jre,而Jre包含了Jvm。从最初开始学习java,直到开始了解jvm,这一个过程是漫长的,首先看看为什么要学习Jvm,学习Jvm后能给我们带来什么样的好处。

从Java的入门,到Web的开发,途中发现很多问题是必须要掌握了Jvm的知识才能去解决一些问题,从而引出了为什么要学习和学习后能带来什么好处的答案。

本图中,在官网有详细的描述,此处不做过多的翻译,相信看官老爷英语水平比我高。

二、从.Java文件到.Class文件

只要会Java语言的开发人员,都知道在Java语言中,.java文件是无法在Jvm中直接运行的,需要通过编译器得到.class文件(俗称字节码文件),有此结论后我们开始实际操作

  • 准备简单的java文件
public class Person {
	private String name;
	private int age;
	private static String address;
	private final static String hobby="Programming";
	public void say(){
		System.out.println("person say...");
	}
	public int calc(int op1,int op2){
		return op1+op2;
	}
}
  • 通过javac命令得到.class文件

        此时得到的.class文件用Notepad++打开时乱码,抱着尝试的心,使用Sublime Text打开,果然得到了不一样的结果。

//NotePad++打开
漱壕   4 '
  	   
     name Ljava/lang/String; age I address hobby 
ConstantValue   <init> ()V Code LineNumberTable say calc (II)I 
SourceFile Person.java   ! " # 
person say... $ % & +com/example/springprovider/prosesser/Person java/lang/Object Programming java/lang/System out Ljava/io/PrintStream; java/io/PrintStream println (Ljava/lang/String;)V !            	 
   
         
                  *??          	        %     	???      
                   `?                
//Sublime Text打开
cafe babe 0000 0034 0027 0a00 0600 1809
0019 001a 0800 1b0a 001c 001d 0700 1e07
001f 0100 046e 616d 6501 0012 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 0100
0361 6765 0100 0149 0100 0761 6464 7265
7373 0100 0568 6f62 6279 0100 0d43 6f6e
7374 616e 7456 616c 7565 0800 2001 0006
3c69 6e69 743e 0100 0328 2956 0100 0443
6f64 6501 000f 4c69 6e65 4e75 6d62 6572
5461 626c 6501 0003 7361 7901 0004 6361
6c63 0100 0528 4949 2949 0100 0a53 6f75
7263 6546 696c 6501 000b 5065 7273 6f6e
2e6a 6176 610c 000f 0010 0700 210c 0022
0023 0100 0d70 6572 736f 6e20 7361 792e
2e2e 0700 240c 0025 0026 0100 2b63 6f6d
2f65 7861 6d70 6c65 2f73 7072 696e 6770
726f 7669 6465 722f 7072 6f73 6573 7365
722f 5065 7273 6f6e 0100 106a 6176 612f
6c61 6e67 2f4f 626a 6563 7401 000b 5072
6f67 7261 6d6d 696e 6701 0010 6a61 7661
2f6c 616e 672f 5379 7374 656d 0100 036f
7574 0100 154c 6a61 7661 2f69 6f2f 5072
696e 7453 7472 6561 6d3b 0100 136a 6176
612f 696f 2f50 7269 6e74 5374 7265 616d
0100 0770 7269 6e74 6c6e 0100 1528 4c6a
6176 612f 6c61 6e67 2f53 7472 696e 673b
2956 0021 0005 0006 0000 0004 0002 0007
0008 0000 0002 0009 000a 0000 000a 000b
0008 0000 001a 000c 0008 0001 000d 0000
0002 000e 0003 0001 000f 0010 0001 0011
0000 001d 0001 0001 0000 0005 2ab7 0001
b100 0000 0100 1200 0000 0600 0100 0000
0900 0100 1300 1000 0100 1100 0000 2500
0200 0100 0000 09b2 0002 1203 b600 04b1
0000 0001 0012 0000 000a 0002 0000 0010
0008 0011 0001 0014 0015 0001 0011 0000
001c 0002 0003 0000 0004 1b1c 60ac 0000
0001 0012 0000 0006 0001 0000 0013 0001
0016 0000 0002 0017 

即使打开了,这TM我也看不懂啊,看不懂先别急,此时我们应该回过头,看看这个字节码文件到底是怎么来的。

当执行Javac命令的时候,会相同文件目录下会生成一个person.class文件,在执行javac命令到生成.class文件之间所作的事情,被称为编译过程。

我想知道编译的时候到底做了哪些事情,首先我们猜想一下设计编译器的作者应该怎么去设计。

  1. 首先要保证提供的.Java文件没有语法错误吧。
  2. 其次是应该进行编译,将对应的语法转换为jvm能认识的字节码吧。

经过一系列的猜测之后,看看字节码到底怎么生成

通过查看官网得到如下结论: 

  • Person.java
  • 词法分析器
  • tokens流
  • 语法分析器
  • 语法树/抽象语法树
  • 语义分析器
  • 注解抽象语法树
  • 字节码生成器
  • Person.class文件

作为Java开发人员,其实个人建议不要过多注重编译的细节,最重的是要保证你的代码没有明显的语法错误或者异常,除非你对编译原理非常感兴趣。

现在搞明白了.class文件如何而来,再回头看看权威机构(官网)是如何解释那些十六进制数的
官网地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

//u1:表示16进制字节码文件中的2位字符
ClassFile {
    u4             magic;                 //u4 sublime text打开文件中的16进制前8位 cafe babe
    u2             minor_version;         //以此类推,u4后的4位数据 0000
    u2             major_version;         // 0034
    u2             constant_pool_count;   //0027
    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];
}
  • magic:标志是一个标准的class文件
  • minor_version, major_version minormajor是不是在哪儿见过? GC中相关知识点吧,这里不进行详细解释,后续会详细解释GC相关知识。
  • constant_pool_count:常量池数量
  • constant_pool[]:描述常量池中的信息
  • access_flags:访问权限标志

.....这里不做过多的描述,相信看官老爷比我英语水平高.......

此时此刻,我觉得单用这么一种方式看字节码,很怪,看起来不得劲,所以我有看看了jdk是否有提供另一种查看字节码文件的方式,于是找到了一个命令javap。

//执行javap命令,该命令的意思是,将Person.class文件分解出来,并且导出到同路径下的person.txt文件中
javap -c Person.class > person.txt
Compiled from "Person.java"
public class com.example.springprovider.prosesser.Person {
  //无参数构造器
  public com.example.springprovider.prosesser.Person();
    Code:
       0: aload_0        //从本地变量表中加载索引为0的变量的值
       1: invokespecial #1  //出栈,调用java/lang/Object."<init>":()V 初始化对象

  public void say();
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;  获取一个静态的输出流
       3: ldc           #3                  // String person say...  输出内容
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 调用println()
       8: return  //返回结果

  public int calc(int, int);
    Code:
       0: iload_1  //从局部变量表中获取op1的值
       1: iload_2  //从局部变量表中获取op2的值
       2: iadd     //做一个加法于运算
       3: ireturn  //从栈帧中拿到方法被调用的地址并且return
}

又陷入了沉思中,这TM又是什么鬼?  继续寻找官网有用信息。

我输了,全是汇编指令,于是查看各种资料,得出结论。

三、class文件的加载

此时看到这几个字联想到的就是 类加载机制 
此时此刻,想到了类加载机制,又联想到了双亲委派模式

1、类加载机制

  1. 将class文件加载到内存中
  2. 对数据进行校验,和对类型转换并且初始化数据
  3. 生成代表类文件的Class对象到内存中

官网的解释:https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.1.2     
标题:12.1.2. Link Test: Verify, Prepare, (Optionally) Resolve

  • 装载(Load)
  1. 将class文件通过全路径获取到二进制流
  2. 将字节流中代表的静态数据转换到方法区的运行时数据
  3. 在堆中生成一个代表该class文件Class对象,作为对方法区中这些数据的访问入口.
  • 链接(Link)
  1. 验证:保证被加载类的正确性
  2. 准备:为类的静态变量分配内存,并将其初始化为默认值, 例如private int age;  int类型的默认值是0
  3. 解析:把类中的符号引用转换为直接引用

    如何理解:把类中的符号引用转换为直接引用

    在之前的javap命令生成的符号,例如:constant_pool_count, 此时此刻,虚拟机只是知道又常量池的存在,而并不知道到底是哪一个常量指向了该常量池的数据,所以该步骤就是将编译后的符号引用转换为内存中内存地址的直接引用。

  • 初始化(Initialize):对类中所有的静态变量和静态代码块进行初始化作用,此时的初始化并不是初始化为各种类型的默认值,而是初始化为程序员在代码中赋的值。

四、类加载器(ClassLoader)

  • 类加载器类型

    Bootstrap ClassLoader:类加载器最顶层的加载器
    作用:负责加载$JAVA_HOME jre/lib/rt.jar 里所有的class或Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader子类。

    Extension ClassLoader:负责加载java平台中扩展功能的一些jar包,包括 $JAVA_HOME 中 jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。

    App ClassLoader:负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包。

    Custom ClassLoader:通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。

  • 图解

        

  • 类加载原理

类加载器在加载类的时候,为了避免同一个类被重复多次加载,采用了双亲委派模式对类进行加载。

如何理解双亲委派(举个栗子):

有爷爷(身高150cm)、父亲(身高170cm)、儿子(身高180cm)三人,儿子发现家里灯泡坏了,儿子自己不会去换灯泡,于是交给父亲去换,而父亲收到换灯泡的请求后,继续往上递交到爷爷,爷爷发现自己身高不足,无法换灯泡,于是递交给下一级(父亲),父亲发现自己还是无法换灯泡,此时只有儿子自己去换灯泡。

在java类加载机制中双亲委派模式与此类似,如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

此处类加载机制就讲完了,如有瑕疵或不足,请看官老爷指出。

运行时数据区博客地址://TODO

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值