深入理解JAVA虚拟机-类文件结构及加载

 在现代编译器中通常会有前端,优化器和后端,前段可以输入不同的语言,后端可以基于目标机器不同的硬件采用不同的后端模块输出。这就保证了编译器的通用性,或者说无关性。

在虚拟机中也是一样,虚拟机和字节码提供了虚拟的架构平台和数据存储格式,是实现语言无关性的基础。关于字节码,百度百科中是这样描述的:Java中,字节码是CPU构架(JVM)的具有可移植性的机器语言。[摘自java in a nutshell]。可以把字节码看成javac编译器编译的前段结果,是虚拟机这个“优化器+部分后端”的输入,由虚拟机进行解释(热点代码会由JIT编译成机器码)。

虚拟机工作的基本单位就是字节码,无论是类、对象、变量等都是由一个一个的class文件组合而成,而这个class文件是一个结构体,以下为部分源码:

public class ClassFile {
    //魔数magic的类型是u4,4个字节CA FE BA BE
    //cafebaye是class文件开始的标志
    public final static int JAVA_MAGIC = 0xCAFEBABE;

    //每一种名称都有对应的类型--->代表字节大小,因为class内是不对齐没空格的
    //依次用tag标记当前是何种类型、方法、版本,以便于验证语法和语义、安全性检查等等
    public final static int CONSTANT_Utf8 = 1;
    public final static int CONSTANT_Unicode = 2;
    public final static int CONSTANT_Integer = 3;
    public final static int CONSTANT_Float = 4;
    public final static int CONSTANT_Long = 5;
    public final static int CONSTANT_Double = 6;
    public final static int CONSTANT_Class = 7;
    public final static int CONSTANT_String = 8;
    public final static int CONSTANT_Fieldref = 9;
    public final static int CONSTANT_Methodref = 10;
    public final static int CONSTANT_InterfaceMethodref = 11;
    public final static int CONSTANT_NameandType = 12;
    public final static int CONSTANT_MethodHandle = 15;
    public final static int CONSTANT_MethodType = 16;
    public final static int CONSTANT_Dynamic = 17;
    public final static int CONSTANT_InvokeDynamic = 18;
    public final static int CONSTANT_Module = 19;
    public final static int CONSTANT_Package = 20;

    public final static int REF_getField = 1;
    public final static int REF_getStatic = 2;
    public final static int REF_putField = 3;
    public final static int REF_putStatic = 4;
    public final static int REF_invokeVirtual = 5;
    public final static int REF_invokeStatic = 6;
    public final static int REF_invokeSpecial = 7;
    public final static int REF_newInvokeSpecial = 8;
    public final static int REF_invokeInterface = 9;

    public final static int MAX_PARAMETERS = 0xff;
    public final static int MAX_DIMENSIONS = 0xff;
    public final static int MAX_CODE = 0xffff;
    public final static int MAX_LOCALS = 0xffff;
    public final static int MAX_STACK = 0xffff;

    public final static int PREVIEW_MINOR_VERSION = 0xffff;

    public enum Version {
        V45_3(45, 3), // base level for all attributes
        V49(49, 0),   // JDK 1.5: enum, generics, annotations
        V50(50, 0),   // JDK 1.6: stackmaps
        V51(51, 0),   // JDK 1.7
        V52(52, 0),   // JDK 1.8: lambda, type annos, param names
        V53(53, 0),   // JDK 1.9: modules, indy string concat
        V54(54, 0),   // JDK 10
        V55(55, 0),   // JDK 11: constant dynamic, nest mates
        V56(56, 0);   // JDK 12
        。。。。。。。。。。。。。。

面向对象中有句很出名的话,一切皆是对象。在这里,常量是对象,方法是对象,类也是对象,这些内容都被编译连同语义集成在class文件中。class是如何来描述一个类的呢?

类有成员属性、方法、以及内部代码。他们有众多共同的部分,如方法和成员属性都要标识类名,如数据类型等等,如果每个人存有一份会造成空间的极大浪费,所以这些公共部分都放在一个公共仓库----常量池,只要自身具有公共仓库--常量池的相应标志即可。

首先从成员属性(同字段表、以下用字段表代替)来说,例如:public int id,简单的看有访问修饰符+数据类型+字段名,其他还需要类名。public在对应访问标志中可以找到,这个方法的类名只需要用常量池字面量就可以表示类全名,int数据类型等等同理。方法表大部分同理,但是对于方法的名称描述比较特殊,如(参数..)I--->返回值为int,另外方法还有代码块,用code数据项保存在class文件中。

 

浅谈类加载机制:对于类的加载机制感觉没有特别了解,只做了一个简图,以及类加载时一个很有趣的例子

简图:

在深入理解JVM这本书中提到,比较两个类是否相等,只有在两个类是同一个类加载器加载的前提下才有意义,也就是说被不同加载器加载的类一定不同。

但是在后面又说到类加载的双亲委派模型,也就是说类加载器会优先由父类加载器执行加载操作,而所有类(除了Object)的父类都是Object,也就是说使用不同的类加载器但最终实现的都会是他们的共同父类加载器。

这让我产生了很大疑惑,后来查阅了相关资料发现答案可能如下:

package org.fenixsoft.classloading;

import java.io.IOException;
import java.io.InputStream;

public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        ClassLoader myLoader = new ClassLoader() {
            /**
             * 类加载器存在双亲委派模型,会默认优先由父类加载器加载
             * 但是这里重写了loadClass方法,正常的loadClass方法会优先实现双亲委派,
             * 但是重写的这个方法并没有委派,所以这里返回值是false
             * @param name
             * @return
             * @throws ClassNotFoundException
             */
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";

                    InputStream is = getClass().getResourceAsStream(fileName);

                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];

                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };

        Object obj = myLoader.loadClass("org.fenixsoft.classloading.ClassLoaderTest").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof org.fenixsoft.classloading.ClassLoaderTest);
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值