zoukankan      html  css  js  c++  java
  • 类加载机制

    导语

    Java语言中类型的加载,连接和初始化都在程序运行期完成的。一个类的生命周期顺序:加载,连接(验证,准备,解析),初始化,使用,卸载。本文从这几个过程来分析。

    加载

    • 找文件:通过一个类的全限定名(包名+类名)来获取定义此类的二进制文件,即我们所说的class文件
    • 加载到内存:将该字节码文件所代表的静态存储(字节码位于磁盘中,我们认为是静态存储)转为方法区的运行时数据结构
    • 在内存中生成一个代表该类的Java.lang.Class对象,作为方法区这个类的各种数据访问入口

    加载的过程是由类加载器ClassLoader完成的,但是数组类本身不通过类加载器创建,它由JVM直接创建

    连接

    验证

    确保class文件中的信息符合当前虚拟机的要求,验证包括文件格式验证,元数据验证,符号引用验证等

    准备

    为类变量分配内存并设置该类变量的初始值(这个初始值并不是我们代码中赋的值,而是所谓的“零值”)

    解析

    将符号引用变为直接引用,在生成的class文件中,我们不知道一个变量将来加载到内存后的实际位置,所以我们先用符号引用来指向这个变量。等class文件加载到内存后,地址被确定好了,就可以将符号引用换成直接引用(具体内存地址)。但是解析是有条件的,只有非虚方法才能被解析(静态方法,私有方法,实例构造器,父类方法和final方法)

    初始化

    编译好的字节码文件有一个< clinit >方法,该方法的作用就是对类变量进行初始化(我们代码中指定的值)。在类初始有以下要求:

    • 子类的方法执行前,父类的< clinit >方法已执行完毕
    • 执行子接口的方法前不需要先执行父接口的< clinit >方法
    • < clinit >方法执行是线程安全的,且只会被执行一次

    类加载器

    类加载器与JVM是独立的,类的加载需要类加载器完成。一个类只能被同一个类加载器加载一次,但是可以被多个类加载器加载。比如Test类可以被A加载器加载,又可以再次被B加载器加载器加载,这个内存中会有两个Test类,但是对于JVM来说这两个类不同,因为它们是由不同的加载器加载的。

    字节码执行

    先来看几个概念:

    物理机

    可执行文件由执行引擎加载执行,引擎与处理器指令集,操作系统息息相关。

    虚拟机

    字节码文件由虚拟机引擎加载,该引擎脱离了对处理器,操作系统的依赖。这也是为什么Java能实现一次编译,到处运行的原因。

    栈帧

    栈帧的概念不仅限于Java,C中也有栈帧。程序的执行其实是方法的不断调用(从main函数开始)。方法被调用时会将局部变量,传入参数等信息压栈。这样就形成了一个栈帧。可以理解为一个方法对应一个栈帧。Java中的栈帧包括局部变量表,操作数栈,动态连接,方法返回地址。

    局部变量表

    存储方法参数和方法内的局部变量,局部变量表中的最小存储单位为槽(slot)。槽的大小并没有明确规定,但是槽中必须能放下byte,char,boolean,short,int float,double和reference类型。

    操作数栈

    局部变量表只是记录了局部变量的信息,但是对局部变量的操作需要在栈上实现,比如c=a+b。
    a,b,c都在局部变量表中,但+操作和赋值操作是在操作数栈上完成的。

    C中的常识

    方法的调用

    方法的调用不等同于方法的执行,方法调用只关心方法的声明,并不关心将来被调用方法在内存中的具体位置

    静态链接

    C文件编译后有个链接的过程,编译时只是确定方法版本,然后建立符号索引。例如:在方法A中调用方法B,只要B被声明过,即使没有方法的具体实现也能编译通过。而在链接阶段则是将符号引用替换为直接引用(B方法在内存中的具体起始位置),这时如果方法B不存在则无法替换,链接失败。

    动态链接

    如果程序中了使用动态库,将符号引用转为直接引用的工作会由加载器完成,而不是链接器。

    注意

    静态类型与实际类型:

    Human man = new Man();
    

    man是一个变量,它有两种类型,即静态类型与实际类型。静态类型是Human类型,这个在编译的时候就能确定。实际类型在程序运行时由其指向的对象所确定的,这里的实际类型就是Man了。区分静态类型和实际类型与域的访问,方法的调用有关。下面看一个简单的例子:

    public class A extends B{
    	int a = 1;
    
     	public void p1() {
        	B b = new A();
         	System.out.println(b.a);
    	}
    }
    
    class B {}
    

    上面的代码中我们编译会出现错误,说B中没有a这个变量。这是因为对于对象中属性的访问都使用静态类型。b的静态类型是B,而B中没有a属性,所以编译会报错。并且静态类型也会对代码的运行产生影响。如果B中有变量a=0,上面的代码就会编译通过,但是输出结果是0。

    分派

    因为方法能被重载和复写,分派的任务就是找到一个合适的方法来执行。

    静态分派:静态分派对应的是函数重载,编译器在编译阶段,会根据我们在调用某个方法时传入的参数(静态类型)来确定调用那个方法。

    动态分派:对应的是方法复写(override),当一个对象向上转型后(Base b = new Child()),调用b的某个方法时,会涉及到动态分配。这时候需要用实际类型来定位了。假如Base中test()方法,Child()中也有test()方法,那么会调用Child()中的test()方法。即方法的调用是由实际类型决定的。

    java指令集参考

    第一个博文写有有点问题
    http://blog.csdn.net/chenzhp/article/details/1798166
    http://www.cnblogs.com/frank-pei/p/5432949.html

    双亲委派模型

    https://blog.csdn.net/zhangliangzi/article/details/51338291

    为什么使用双亲委派

    第一个原因是防止重复加载,子ClassLoader要加载一个类时看父ClassLoader是否加载,如果加载过就没必要加载了。还有的就是安全问题,如果不使用委托模式,那么可以自定义一个核心类(比如String类)来动态替代Java核心类,这样存在非常大的安全隐患。

  • 相关阅读:
    高级特性(4)- 数据库编程
    UVA Jin Ge Jin Qu hao 12563
    UVA 116 Unidirectional TSP
    HDU 2224 The shortest path
    poj 2677 Tour
    【算法学习】双调欧几里得旅行商问题(动态规划)
    南洋理工大学 ACM 在线评测系统 矩形嵌套
    UVA The Tower of Babylon
    uva A Spy in the Metro(洛谷 P2583 地铁间谍)
    洛谷 P1095 守望者的逃离
  • 原文地址:https://www.cnblogs.com/xidongyu/p/6433982.html
Copyright © 2011-2022 走看看