JVM源码剖析之SymbolTable和StringTable

news2024/7/7 16:12:38

很多读者在观看JVM相关的书籍时会看到SymbolTable和StringTable,书中的三言二语介绍的不是很清楚,并且读者的水平有限,导致无法理解SymbolTable和StringTable。所以特意写此篇图文并茂的文章来彻底理解SymbolTable和StringTable这两张表。

版本信息如下:

jdk版本:jdk8u40

因为Hotspot是c++构成,所以也存在面向对象的思想,也即存在类和对象,所以直接看到SymbolTable和StringTable的类定义即可。src/share/vm/classfile/symbolTable.hpp 文件中

// key为Symbol,value为mtSymbol
class SymbolTable : public Hashtable<Symbol*, mtSymbol> {
  friend class VMStructs;
  friend class ClassFileParser;

  // The symbol table
  static SymbolTable* _the_table;
  
}
// key为oop,value为mtSymbol
class StringTable : public Hashtable<oop, mtSymbol> {
  friend class VMStructs;

  // The string table
  static StringTable* _the_table;
}

可以非常清楚的看到2者都继承了Hashtable,也更加肯定2者就是一张表。而Hashtable可以理解为Java中HashMap结构(数组+链表+特定条件下的红黑树)。而谈到map结构一定会出现key,value的映射关系。

SymbolTable:key是Symbol,Symbol可以理解为utf8编码的字符信息

SymbolTable:value是mtSymbol,这是一个枚举值,仅仅表示内存的解释,不起实际作用

——————————————————————

StringTable:key是oop,oop可以理解为Java对象地址(实际上存放的就是Java的String对象)

StringTable:value是mtSymbol,这是一个枚举值,仅仅表示内存的解释,不起实际作用

 

既然已经明白SymbolTable和StringTable的大致作用了,那么下面就是源码查看如何使用。

SymbolTable使用

从上文,我们清楚明白SymbolTable中存放的是Symbol对象,而Symbol对象存放的是utf8编码的字符。所以utf8编码的字符来自何处呢? 

下面从一个很简单的例子来分析

public class demo{
  public static void main(String[] args)  {
     System.out.println("123");
  }
}

简单的查看一下字节码的表示

Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // 123
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // demo
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               demo.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               123
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               demo
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String 123
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 4: 0
        line 5: 8
}
SourceFile: "demo.java"

可以看到字节码常量池中存在很多Utf8的字段,那是不是这些常量池中Utf8的字段会解析成Symbol对象呢?抱着疑问,我们看到Hotspot中解析字节码常量池的源码。src/share/vm/classfile/classFileParser.cpp 文件中。

void ClassFileParser::parse_constant_pool_entries(int length, TRAPS) {

  // 解析常量池,从下标#1开始
  for (int index = 1; index < length; index++) {

    // 拿到下标对应的tag,比如拿到Utf8、Class、String、NameAndType等等....
    u1 tag = cfs->get_u1_fast();

    // 根据一个字节的tag区分后续。
    switch (tag) {
      
      ………… 
      // 省略了其他tag的解析,我们只关心Utf8的解析。

      case JVM_CONSTANT_Utf8 :
        {
          cfs->guarantee_more(2, CHECK);  // utf8_length
          // 根据长度解析
          u2  utf8_length = cfs->get_u2_fast();
          u1* utf8_buffer = cfs->get_u1_buffer();

          unsigned int hash;
          // 从SymbolTable中尝试获取,如果存在就直接获取,如果不存在就创建。
          Symbol* result = SymbolTable::lookup_only((char*)utf8_buffer, utf8_length, hash);
          if (result == NULL) {
            names[names_count] = (char*)utf8_buffer;
            lengths[names_count] = utf8_length;
            indices[names_count] = index;
            hashValues[names_count++] = hash;
            // 把Symbol添加到SymbolTable中
            // 因为在常量池中最多的就是Utf8项,所以为了优化,这里采用批处理
            // 如果当前常量池中Utf8项数量每8的倍数就一次性插入一轮。
            if (names_count == SymbolTable::symbol_alloc_batch_size) {
              SymbolTable::new_symbols(_loader_data, _cp, names_count, names, lengths, indices, hashValues, CHECK);
              names_count = 0;
            }
          } else {
            // 添加到常量池中。
            _cp->symbol_at_put(index, result);
          }
        }
        break;
      default:
        classfile_parse_error(
          "Unknown constant tag %u in class file %s", tag, CHECK);
        break;
    }
  }
  // 把Symbol添加到SymbolTable中
  // 如果没有批处理,那终究还是得插入。
  if (names_count > 0) {
    SymbolTable::new_symbols(_loader_data, _cp, names_count, names, lengths, indices, hashValues, CHECK);
  }
}

这里解析Utf8项,拿到Utf8的值,上文字节码常量池第#18项的 123 ,尝试去SymbolTable中拿到123对应的Symbol,如果不存在就创建Symbol对象并添加到SymbolTable,如果存在就直接获取Symbol对象放入到常量池对象中。

除了常量池第#18项,还有第#21、#22、#23、#24、#25...... 众多Utf8项。并且其他常量池项最终都是指向到Utf8项,所以也能看明白Utf8项或者说Symbol的作用是啥了。存放类名、字符串数据、方法签名、方法名。一言以蔽之:Utf8项最终会解析成Symbol,而Symbol存放Java程序中所需的元数据、真实数据。而SymbolTable作为一个载体存放所有的Symbol

StringTable使用

还是使用SymbolTable的案例。

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // 把2号常量池的静态变量押入操作数栈
         3: ldc           #3                  // 把3号常量池的字符串解析成String对象,并且押入操作数栈
         5: invokevirtual #4                  // 执行4号常量池的方法,并且消耗2个操作数栈
         8: return                          
      LineNumberTable:
        line 4: 0
        line 5: 8

 所以,我们需要去源码论证ldc字节码指令如何创建出String对象。这里为了源码简单,使用C++字节码解释器作为了论证。src/share/vm/interpreter/bytecodeInterpreter.cpp 文件

CASE(_ldc):
{
  …………
  省略其他的处理

  ConstantPool* constants = METHOD->constants();
  switch (constants->tag_at(index).value()) {

  …………
  省略其他的处理

  case JVM_CONSTANT_String:
    {
      // 从常量池的对象池中拿对象
      oop result = constants->resolved_references()->obj_at(index);
      // 如果不存在
      if (result == NULL) {
        // 解析ldc,生成String对象。
        CALL_VM(InterpreterRuntime::resolve_ldc(THREAD, (Bytecodes::Code) opcode), handle_exception);
        // 线程变量是可以在线程中任意地方存取,并且线程安全。
        // 这里把String对象添加到操作数栈中
        SET_STACK_OBJECT(THREAD->vm_result(), 0);
        THREAD->set_vm_result(NULL);
      } else {    // 如果存在就直接添加到操作数栈中。
        VERIFY_OOP(result);
        SET_STACK_OBJECT(result, 0);
      }
    break;
    }

    …………
    省略其他的处理
  }
  UPDATE_PC_AND_TOS_AND_CONTINUE(incr, 1);
}

继续往InterpreterRuntime::resolve_ldc 方法看

IRT_ENTRY(void, InterpreterRuntime::resolve_ldc(JavaThread* thread, Bytecodes::Code bytecode)) {
  assert(bytecode == Bytecodes::_fast_aldc ||
         bytecode == Bytecodes::_fast_aldc_w, "wrong bc");
  ResourceMark rm(thread);
  methodHandle m (thread, method(thread));
  Bytecode_loadconstant ldc(m, bci(thread));
  // 解析
  oop result = ldc.resolve_constant(CHECK);
  thread->set_vm_result(result);
}
IRT_END

oop Bytecode_loadconstant::resolve_constant(TRAPS) const {
  assert(_method.not_null(), "must supply method to resolve constant");
  int index = raw_index();
  ConstantPool* constants = _method->constants();
  // 解析
  return constants->resolve_constant_at(index, THREAD);
}

oop ConstantPool::resolve_constant_at_impl(constantPoolHandle this_oop, int index, int cache_index, TRAPS) {
  oop result_oop = NULL;
  Handle throw_exception;
  int tag_value = this_oop->tag_at(index).value();

  switch (tag_value) {
  …………
  省略其他的处理

  case JVM_CONSTANT_String:
    // 拿到String对象
    result_oop = string_at_impl(this_oop, index, cache_index, CHECK_NULL);
    break;

  …………
  省略其他的处理
  
  }

  …………
  省略其他的处理
  return result_oop;
}

oop ConstantPool::string_at_impl(constantPoolHandle this_oop, int which, int obj_index, TRAPS) {
  // 从常量池中的对象池中尝试拿到缓存对象。
  oop str = this_oop->resolved_references()->obj_at(obj_index);
  if (str != NULL) return str;

  // 拿到ldc指向常量池下标最终对应的Utf8项
  // 而从上文讲述的SymbolTable可以得知,Utf8项在JVM中使用Symbol对象表示。
  // 所以这里拿到Symbol对象,而拿到Symbol对象,就拿到了具体数据
  Symbol* sym = this_oop->unresolved_string_at(which);

  // 尝试从StringTable中拿到String对象,如果存在就返回,如果不存在就创建并返回。
  str = StringTable::intern(sym, CHECK_(NULL));
  // 把对象添加到常量池中的对象池中
  this_oop->string_at_put(which, obj_index, str);
  return str;
}

由于调用栈比较深,所以这里对以上的代码做一个总结:

  1. 拿到 ldc 字节码指令指向常量池的代表,拿案例来说,也即拿到下标#3,也即拿到常量池String项
  2. String项指向下标#18 Utf8项
  3. 而从上文讲述的SymbolTable可以得知,Utf8项在JVM中使用Symbol对象表示。所以这里拿到Symbol对象,而拿到Symbol对象,就拿到了具体数据,也即拿到具体数据 123
  4. 拿到Symbol对象后会调用StringTable::intern方法,所以下文继续关注此方法
oop StringTable::intern(Symbol* symbol, TRAPS) {
  if (symbol == NULL) return NULL;
  ResourceMark rm(THREAD);
  int length;
  // 把utf8字符串转换成unicode编码。
  jchar* chars = symbol->as_unicode(length);
  Handle string;
  oop result = intern(string, chars, length, CHECK_NULL);
  return result;
}


oop StringTable::intern(Handle string_or_null, jchar* name,
                        int len, TRAPS) {
  // 上文得知,StringTable就是一张hash表。所以这里计算下标。
  unsigned int hashValue = hash_string(name, len);
  int index = the_table()->hash_to_index(hashValue);
  oop found_string = the_table()->lookup(index, name, len, hashValue);

  // 命中缓存,直接返回
  if (found_string != NULL) return found_string;

  // 因为没有命中缓存,所以需要创建一个String对象,并且添加到StringTable中
  // 在Java堆创建一个String对象。
  string = java_lang_String::create_from_unicode(name, len, CHECK_NULL);

  // 把创建出来的String对象,添加到StringTable中。
  return the_table()->basic_add(index, string, name, len,
                                hashValue, CHECK_NULL);
}

这里就非常明显了,把Symbol对象中的值拿出来,然后去StringTable中尝试命中缓存,如果命中就直接返回。如果没有命中就创建Java的String对象并添加到StringTable中。

所以,一言以蔽之:StringTable 管理了多个Java中String对象。而这些String对象是根据常量池String项对应的Utf8项(Symbol)生成的

SymbolTable和StringTable区别

从上文对SymbolTable和StringTable的介绍完全可以得知,这两张表的职责完全不一样。SymbolTable是存储Java项目中元数据、真实数据。而StringTable是储存Java中String对象。

硬要说有点关联的话,就是StringTable的数据来源于SymbolTable。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/718394.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java面试Day16

1.Dubbo 是什么&#xff1f;是否了解过它的架构设计&#xff1f; Dubbo官网&#xff1a;Apache Dubbo Dubbo是一个高性能、轻量级的开源Java RPC框架&#xff0c;它提供了完整的RPC协议栈&#xff0c;包括服务发布、服务引用、负载均衡、容错、服务治理和服务监控等功能&#…

构造函数使用初始化列表+模板

构造函数使用初始化列表模板 注意对应关系&#xff1a; Stack(int size) ;template<class DataType> inline Stack<DataType>::Stack(int size) : stacksize(size), top(0) {item new DataType[stacksize];if (item ! nullptr) cout << "成功初始化栈&…

再见!Fastjson!

你为何仍用Fastjson&#xff1f; 原因可以说出5678种&#xff0c;总而言之言而总之&#xff0c;你不&#xff08;敢&#xff09;切换的原因或许只有一个&#xff1a;Fastjson的静态方法调用用着省心&#xff1b;最重要的是&#xff0c;对其它JSON库&#xff08;如Jackson/Gson…

【生活分享】《哈姆雷特》· 苏州站 - 一次有趣的音乐剧体验

平时博客里大部分都是写技术相关的文章&#xff0c;感觉内容还是比较单一的&#xff0c;也想分享一下最近的一些有趣的生活~ 6月30号的时候&#xff0c;便订好了徐俊导演编排的7月2号场《哈姆雷特》音乐剧。而实际上&#xff0c;苏州场7月1号就上映了&#xff0c;感觉那天太晚……

C++图形开发(6):落下后能弹起的小球

文章目录 1.重复下落的小球2.落下后能弹起的小球3.能上下反弹的小球4.符合重力的能上下反弹的小球 今天我们来尝试实现一个落地后可以弹起的小球 1.重复下落的小球 首先&#xff0c;我们要来实现一个小球的重复下落 我们知道&#xff0c;在前面的代码中&#xff08;详见C图形…

云his源码:医疗卫生信息系统

伴随着以5G、云计算、大数据、人工智能和物联网等新兴技术的发展&#xff0c;医疗行业也呈现了数字化的发展趋势。如何让医疗领域中的服务提供方、需求方、支付方以及供应链等参与方实现业务协同及数据协同&#xff0c;是目前医疗行业所需的能力。 HIS系统&#xff0c;即“医疗…

点云数据分类及滤波方法

如何获取点云数据 传统的点云获取技术包括非接触式测量和接触式测量两种&#xff0c;它们的主要区别在于&#xff0c;在测量过程中测头是否与工件的表面相接触。 非接触式测量是利用光学原理的方法采集数据&#xff0c;例如结构光法、测距法以及干涉法等。该方法的优点在于测…

vue项目中的vue.config.js配置文件中的proxy代理post一直在预检

记录一个比较有意思的bug,帮别人调试遇到的&#xff0c;一个哥们一直在群里问了好几次同一个问题了&#xff0c;他配置了代理&#xff0c;请求一直在发送&#xff0c;postman测试没问题&#xff0c;可以成功接收到&#xff0c;但是在项目前端请求&#xff0c;确实一点响应没有&…

黑芝麻智能科技、上海紫先面试(部分)(未完全解析)

黑芝麻智能科技 Hystrix可以限流吗&#xff1f;客户端限流&#xff0c;是限制对下游&#xff08;被调用方&#xff09;的访问&#xff0c;不是对本服务限流。从HystrixCommand的.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)也可以看出来&#xff0c…

C++ -- 异常

文章目录 1. C语言传统处理2. C异常概念3. 异常的使用3.1 异常抛出并没有被捕获3.2 正确使用3.3 捕获异常采用类型匹配就近原则3.4 catch(...)可以捕获任意类型异常3.5 抛出派生类对象使用基类捕获3.6 异常重新抛出3.7 匹配规则3.8 异常规范3.9 异常安全 4. 自定义异常体系5. 异…

05.动态协议OSPF基础

文章目录 **OSPF简介****OSPF和RIP的区别****RIPV2和OSPFV2相同点****不同点** OSPF基础概念OSPF区域OSPF路由类型区域划分的要求**OSPF多区域****Router ID****Router ID选取规则****度量值** OSPF数据包OSPF状态机条件匹配 OSPF的工作过程OSPF基本配置**OSPF的拓展配置** OSP…

番外12:获取晶体管漏极电流源平面的仿真波形

番外12&#xff1a;获取晶体管漏极电流源平面的仿真波形 老是有同学反应&#xff0c;仿真中无法获取漏极电流源平面的仿真波形&#xff0c;在此给出解释与解决办法。 1、ADS能够访问到部分器件的电流源平面波形 对于某些器件&#xff0c;比如说CGH40010F&#xff0c;可以在仿…

硬核了解一下内核链表

一&#xff0c;内核链表定义 言以蔽之&#xff0c;内核链表就是个带头结点的循环双链表。 普通的链表是将next指针定义成为与该结构体一样的类型&#xff0c;这样做通用性不好。与普通的链表的定义和使用方式不一样&#xff0c;内核的链表定义成为了一种通用的结构&#xff1a…

vue打包到生产环境

1.进入到项目根目录执行 npm run build此时会自动打包在dist目录下 2.安装服务 npm install -g serve3.启动 serve dist以上是生产环境打包的过程。 npm run dev 是开发环境, npm run build 是生产环境

Springboot 错用list.stream , 遭遇list浅拷贝偷袭,实战图解

前言 相信很多看客都听闻过深拷贝、浅拷贝 &#xff0c; 但是在日常使用的过程中&#xff0c;是否真的有关心过或者遭遇过呢&#xff1f; 不啰嗦&#xff0c;一起来看看。 正文 接下来我通过示例&#xff0c;来复现一下 list.stream浅拷贝 这个事 &#xff1a; 首先是一个对象…

Nacos服务注册成功,RestTemplate调用服务提供者时空指针异常(已解决)

发现问题并进行记录 目录 项目场景&#xff1a;问题描述原因分析解决 项目场景&#xff1a; RestTemplate也可以做服务调用(进行测试) 目的:解决该问题,不考虑其他远程调用方式(Fegin,Dubbo) 项目Pom文件 Spring Boot ----2.6.3 Spring Cloud------2021.0.1 Spring Cloud Al…

Elasticsearch介绍和安装

ELK简介 Elastic Stack核心产品包括Elasticsearch、Logstash、Kibana&#xff08;也称为ELK&#xff09;和Beats等等。能够安全可靠地获取任何来源、任何格式的数据&#xff0c;然后实时地对数据进行搜索、分析和可视化 Kibana是一个免费且开放的用户界面&#xff0c;能…

Apikit 自学日记:可以对 API 发评论

该功能仅供API研发管理企业版 您可以直接在API文档上发布评论&#xff0c;所有的沟通内容都会跟随API文档保留下来并且按照版本分类好&#xff0c;而不是零散地存在各种聊天工具中。 发表评论 在API详情页&#xff0c;点击 评论 按钮&#xff0c;在弹窗中可以输入评论信息并点…

FPGA实验一:层次法设计组合电路(加法器)

目录 一、实验目的 二、实验要求 三、实验代码 四、实验结果及分析 1、引脚锁定 2、仿真波形及分析 3、下载测试结果及分析 五、实验心得 一、实验目的 &#xff08;1&#xff09;掌握基本组合逻辑电路的 FPGA实现&#xff1b; &#xff08;2&#xff09;学习 Verilo…

使用安装器安装windows gcc— MinGW及让windows像Linux一样的MSYS

这里使用installer进行安装GCC&#xff0c;主要是installer安装其它依赖&#xff0c;如MSYS&#xff0c;autoconf,automake&#xff0c;libtool&#xff0c;vim,perl,mingw-developer-toolkit等&#xff0c;mingw下载地址&#xff0c;如图download链接的mingw-get-setup.exe文件…