• 文件流


    在前一篇介绍ClassFileParser类时简单提了一下_stream属性,这个属性保存的是字节码文件流。如果要读取Class文件的内容,首先需要获取文件对应的字节流,ClassFileStream 内部维护了一个buffer,该buffer指向Class文件所对应的字节流。

    ClassFileStream对象是在ClassLoader::load_classfile()函数中创建的,这个方法在之前介绍类的双亲委派机制时提到过,当装载一个类时,可能会调用到SystemDictionary::load_instance_class()函数,而这个函数会体现出“双亲委派”的逻辑。如果使用启动类加载器,那么可能需要调用load_classfile()方法装载类。load_classfile()方法的实现如下:

    源代码位置:src/share/vm/classfile/classLoader.cpp
    instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) {
    
      stringStream st;
      st.print_raw(h_name->as_utf8());
      st.print_raw(".class");
      const char* name = st.as_string(); // 通过st获取对应的文件名
    
      // Lookup stream for parsing .class file
      ClassFileStream* stream = NULL;
      {
        ClassPathEntry* e = _first_entry;
        while (e != NULL) {
          stream = e->open_stream(name, CHECK_NULL);
          if (stream != NULL) {
            break;
          }
          e = e->next();
        }
      }
      ...
    }
    

    遍历class_path找到要加载的类文件,获取到文件的绝对路径后就创建ClassFileStream对象。ClassPathEntry 是一个链表结构(因为class path有多个),同时在ClassPathEntry中还声明了一个虚函数open_stream()。这样就可以通过循环遍历链表上的结构,直到查找到某个路径下名称为name的文件为止,这时候open_stream()函数会返回ClassFileStream实例。

    在load_classfile()方法中获取到ClassFileStream实例后会调用ClassFileParser类中的parseClassFile()方法,如下:

    instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) {
      // ...
    
      instanceKlassHandle h;
      if (stream != NULL) {
        // class file found, parse it
        ClassFileParser parser(stream);
        ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data();
        Handle protection_domain;
        TempNewSymbol parsed_name = NULL;
        instanceKlassHandle result = parser.parseClassFile(h_name,loader_data,protection_domain,parsed_name,false,CHECK_(h));
        // add to package table
        if (add_package(name, classpath_index, THREAD)) {
          h = result;
        }
      }
    
      return h;
    }
    

    调用parseClassFile()方法后返回表示Java类的instanceKlass对象,最终方法返回的是操作instanceKlass对象的句柄instanceKlassHandle。下一篇开始将详细介绍parseClassFile()方法的实现。

    简单介绍一下ClassFileStream类中的一些被频繁调用的方法,如下:

    u1 ClassFileStream::get_u1(TRAPS) {
      return *_current++;
    }
    
    u2 ClassFileStream::get_u2(TRAPS) {
      u1* tmp = _current;
      _current += 2;
      return Bytes::get_Java_u2(tmp);
    }
    
    u4 ClassFileStream::get_u4(TRAPS) {
      u1* tmp = _current;
      _current += 4;
      return Bytes::get_Java_u4(tmp);
    }
    
    u8 ClassFileStream::get_u8(TRAPS) {
      u1* tmp = _current;
      _current += 8;
      return Bytes::get_Java_u8(tmp);
    }
    
    void ClassFileStream::skip_u1(int length, TRAPS) {
      _current += length;
    }
    
    void ClassFileStream::skip_u2(int length, TRAPS) {
      _current += length * 2;
    }
    
    void ClassFileStream::skip_u4(int length, TRAPS) {
      _current += length * 4;
    }
    

    Class文件由字节为单位的字节流组成,所有的16位、32位和64位长度的数据将被构造成 2个、4个和8个8字节单位来表示。多字节数据项总是按照Big-Endian的顺序进行存储,而x86等处理器则是使用了相反的Little-Endian顺序来存储数据。
    因此,在x86平台上需要进行转换。代码如下:

    源代码位置:openjdk/hotspot/src/cpu/x86/vm/bytes_x86.hpp
    // Efficient reading and writing of unaligned unsigned data in Java
    // byte ordering (i.e. big-endian ordering). Byte-order reversal is
    // needed since x86 CPUs use little-endian format.
    static inline u2   get_Java_u2(address p)           { return swap_u2(get_native_u2(p)); }
    static inline u4   get_Java_u4(address p)           { return swap_u4(get_native_u4(p)); }
    static inline u8   get_Java_u8(address p)           { return swap_u8(get_native_u8(p)); }
    

    调用的相关函数如下:  

    源代码位置:openjdk/hotspot/src/cpu/x86/vm/bytes_x86.hpp
    // Efficient reading and writing of unaligned unsigned data in platform-specific byte ordering
    // (no special code is needed since x86 CPUs can access unaligned data)
    static inline u2   get_native_u2(address p)         { return *(u2*)p; }
    static inline u4   get_native_u4(address p)         { return *(u4*)p; }
    static inline u8   get_native_u8(address p)         { return *(u8*)p; }
    

    调用的swap_u<x>系列的函数实现如下  

    源代码位置:openjdk/hotspot/src/os_cpu/linux_x86/vm/bytes_linux_x86.inline.hpp
    inline u2   Bytes::swap_u2(u2 x) {
      return bswap_16(x);
    }
    inline u4   Bytes::swap_u4(u4 x) {
      return bswap_32(x);
    }
    inline u8 Bytes::swap_u8(u8 x) {	
      return bswap_64(x);
    }
    

    如上是针对基于Linux内核的ubuntu的x86架构下64位版本代码的实现。其中调用的bswap_<x>系列函数是gcc提供的几个内建函数。
    由于HotSpot需要跨平台兼容,所以会增加一些针对各平台的特定实现,如Bytes::swap_u2()函数的完整实现如下:

    inline u2   Bytes::swap_u2(u2 x) {
    #ifdef AMD64
      return bswap_16(x);
    #else
      u2 ret;
      __asm__ __volatile__ (
        "movw %0, %%ax;"
        "xchg %%al, %%ah;"
        "movw %%ax, %0"
        :"=r" (ret)      // output : register 0 => ret
        :"0"  (x)        // input  : x => register 0
        :"ax", "0"       // clobbered registers
      );
      return ret;
    #endif // AMD64
    }
    

    其中的AMD64表示x86架构下的64位指令集,所以笔者当前的机器会选择AMD64位下的实现。如果是非AMD64位的系统,使用gcc内联汇编来实现相关的功能,其将x 的值读入某个寄存器,然后在指令中使用相应寄存器,并将该值移动到%ax中,然后通过xchg 交换%eax中的高低位。然后将最终的结果送入某个寄存器,最后将该结果送到ret中。 

    相关文章的链接如下:

    1、在Ubuntu 16.04上编译OpenJDK8的源代码 

    2、调试HotSpot源代码

    3、HotSpot项目结构 

    4、HotSpot的启动过程 

    5、HotSpot二分模型(1)

    6、HotSpot的类模型(2)  

    7、HotSpot的类模型(3) 

    8、HotSpot的类模型(4)

    9、HotSpot的对象模型(5)  

    10、HotSpot的对象模型(6) 

    11、操作句柄Handle(7)

    12、句柄Handle的释放(8)

    13、类加载器 

    14、类的双亲委派机制 

    15、核心类的预装载

    16、Java主类的装载  

    17、触发类的装载  

    18、类文件解析 

    作者持续维护的个人博客classloading.com

    关注公众号,有HotSpot源码剖析系列文章!

     

      

      

  • 相关阅读:
    php类自动加载
    tp5自定义分页参数
    cURL error 60: SSL certificate problem...
    ajax动态刷新的元素,导致绑定事件失效
    thinkphp5省市区三级联动例子
    restful状态码常用
    mysql的like子句
    mysql官方的测试数据库employees超30万的数据,安装方法介绍
    Aes加解密,php
    php5.6,Ajax报错,Warning: Cannot modify header information
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/13358460.html
Copyright © 2020-2023  润新知