类文件解析001

此处我们开始踏上类文件解析的道路.在此之前,我们已经讲解了类加载的流程,而在类加载的流程的过程中,涉及到类文件解析的过程,由于过程很复杂,因此用一个系列来进行介绍.(其实,在之前介绍的类加载的流程可以准确的).在此处贴下之前的博客目录:


解析

此处先介绍一下ClassFileParser::parseClassFile的核心步骤:

  1. 读取魔数并进行校验其是否为0xCAFEBABE

  2. 读取class文件主,次版本号及验证,检查版本号支持是否支持,若不支持,则抛出java.lang.UnsupportedClassVersionError

  3. 解析常量池

  4. 读取访问标识

    1. 处理JVM_ACC_INTERFACE的后向兼容问题,如果访问标记为JVM_ACC_INTERFACE,并且主版本号小于50,则同时设置JVM_ACC_ABSTRACT,保证后向兼容
    2. 验证类的修饰符并设置
  5. 读取当前类索引,并按索引在常量池项中找到当前类的全限定名

  6. 读取父类索引,并按索引在常量池中找到父类的全限定名和父类句柄

    1. 如果是为0的话,则检查其类名是否是java.lang.Object
    2. 检查其对应的类型为klass,检查其类型不是数组,如果是数组,则抛出异常
  7. 读取接口信息,接口类型包括本地接口和父类传递接口

    1. 如果接口不存在,则返回一个空数组
    2. 解析接口
  8. 解析类变量,并计算出域大小和偏移量(oop-map)信息,并根据域分配策略对字段存储顺序进行分配.这些信息都将在后续步骤填入instanceKlass对象中成为类信息的一部分

  9. 读取方法信息.根据从class中解析出的method信息创建了methodOop对象

  10. 解析父类

  11. 根据已解析的父类,方法,接口等信息得到vtable,itable 大小

  12. 计算字段的偏移量

  13. 创建当前类instanceKlass并按照上述步骤已解析好的信息为该对象赋值,在这期间,同时创建了JAVA镜像类(mirror)

  14. 解析类文件中的属性

  15. 通知类已加载,跟新perfdata计数器


ClassFileStream

在进行class 文件解析时,是使用ClassFileStream来进行读取的,其类图如下:

在这里插入图片描述

ClassFileStream 其内部维护了一个buffer,该buffer指向class 文件所对应的byte流.
ClassFileStream 不具有对buffer释放的操作,调用者有义务进行buffer的释放,同时在构建流时适当地进行资源标记
该类的字段所代表的意义如下:

  • _buffer_start --> 该buffer的起始下标地址
  • _buffer_end --> 该buffer的结束下标地址
  • _current --> 当前所指向的位置
  • _source --> 该ClassFileStream所指向的buffer所对应的源路径(目录名,ZIP/JAR名)
  • _need_verify --> 如果true,则会在解析class file时进行验证

创建过程

ClassFileStream 是在ClassLoader::load_classfile方法中,通过遍历class_path 找到要加载的类时进行创建的.其代码如下:

ClassFileStream* stream = NULL;
ClassPathEntry* e = _first_entry;
.....
 stream = e->open_stream(name);

此处,在介绍一下ClassPathEntry. 其类图如下:

在这里插入图片描述
可见ClassPathEntry 是一个链表结构(因为class path有多个),同时在ClassPathEntry中还声明了一个虚方法,virtual ClassFileStream* open_stream(const char* name) = 0.

此处我们就来看一下ClassPathDirEntry对于该方法的实现.代码如下:

ClassFileStream* ClassPathDirEntry::open_stream(const char* name) {
  // construct full path name 1.构造完成路径
  char path[JVM_MAXPATHLEN];
  if (jio_snprintf(path, sizeof(path), "%s%s%s", _dir, os::file_separator(), name) == -1) {
    return NULL;
  }
  // check if file exists
  struct stat st;
  if (os::stat(path, &st) == 0) { // 2. 检查该路径是否存在,如果存在的话,则进行读取
    // found file, open it
    int file_handle = os::open(path, 0, 0);
    if (file_handle != -1) {
      // read contents into resource array 3.读取内容
      u1* buffer = NEW_RESOURCE_ARRAY(u1, st.st_size);
      size_t num_read = os::read(file_handle, (char*) buffer, st.st_size);
      // close file 4. 关闭文件
      os::close(file_handle);
      // construct ClassFileStream
      if (num_read == (size_t)st.st_size) {
        if (UsePerfData) {
          ClassLoader::perf_sys_classfile_bytes_read()->inc(num_read);
        }
        // 5. 构造ClassFileStream
        return new ClassFileStream(buffer, st.st_size, _dir);    // Resource allocated
      }
    }
  }
  return NULL;
}

其中,第5步会执行ClassFileStream的构造函数,其代码如下:

ClassFileStream::ClassFileStream(u1* buffer, int length, char* source) {
  _buffer_start = buffer;
  _buffer_end   = buffer + length;
  _current      = buffer;
  _source       = source;
  _need_verify  = false;
}


ClassFileParser

ClassFileParser 其内部定义了一系列的方法进行class文件,在读取class文件内容是通过ClassFileStream来获取的. 其方法会在后续的过程中进行讲解的.

其构造函数如下:

ClassFileStream* _stream;              // Actual input stream

ClassFileParser(ClassFileStream* st) { set_stream(st); }
 
void set_stream(ClassFileStream* st)             { _stream = st; }

解析类文件

解析类文件是在ClassLoader::load_classfile中调用的,代码如下:

ClassFileParser parser(stream);
Handle class_loader;
Handle protection_domain;
symbolHandle parsed_name;
instanceKlassHandle result = parser.parseClassFile(h_name,
                                                   class_loader,
                                                   protection_domain,
                                                   parsed_name,
                                                   false,
                                                   CHECK_(h));

其调用的方法如下:

instanceKlassHandle parseClassFile(symbolHandle name,
                                 Handle class_loader,
                                 Handle protection_domain,
                                 symbolHandle& parsed_name,
                                 bool verify,
                                 TRAPS) {
KlassHandle no_host_klass;
return parseClassFile(name, class_loader, protection_domain, no_host_klass, NULL, parsed_name, verify, THREAD);
}

最终调用的方法原型如下:

instanceKlassHandle parseClassFile(symbolHandle name,
                                     Handle class_loader,
                                     Handle protection_domain,
                                     KlassHandle host_klass,
                                     GrowableArray<Handle>* cp_patches,
                                     symbolHandle& parsed_name,
                                     bool verify,
                                     TRAPS);

这里涉及的参数值总结如下:

  • name --> 要加载class文件的名称,含.class
  • class_loader --> 因为此时是在jvm初始化的过程中,因此该Handle 内部所指向的oop 为null.
  • protection_domain, parsed_name, no_host_klass --> 这三者所对应的Handle 内部所指向的oop 为null.
  • verify --> false
  • cp_patches --> NULL

剩余过程,后续介绍…

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值