JVM源码剖析之SymbolTable和StringTable

很多读者在观看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 tablestatic SymbolTable* _the_table;}
// key为oop,value为mtSymbol
class StringTable : public Hashtable<oop, mtSymbol> {friend class VMStructs;// The string tablestatic 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;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #3                  // String 1235: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 4: 0line 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;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic     #2                  // 把2号常量池的静态变量押入操作数栈3: ldc           #3                  // 把3号常量池的字符串解析成String对象,并且押入操作数栈5: invokevirtual #4                  // 执行4号常量池的方法,并且消耗2个操作数栈8: return                          LineNumberTable:line 4: 0line 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_ENDoop 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.hqwc.cn/news/13231.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mac电脑上,webm格式怎么转换成mp4?

mac电脑上&#xff0c;webm格式怎么转换成mp4&#xff1f;webm格式的视频也是最近几年也越来越多的&#xff0c;小编最近就不止一次的下载到过webm格式的视频&#xff0c;很多小伙伴肯定对它还并不是很了解&#xff0c;webm是由谷歌公司所提出以及开发出来的视频文件格式&#…

debian to go

可以使用虚拟机操作&#xff0c;在运行镜像到安装步骤时选择 u盘 不需要手动分 /boot 分区之类的&#xff0c;“Automaction”自动分区就行&#xff0c;全安装到根目录。boot load 安装到 /dev/sdb&#xff0c;也就是硬盘本身 推荐使用gpt分区表&#xff0c;建议拿不用的盘练…

MySQL表单查询

根据题目完成下列要求 CREATE TABLE emp ( empno int(4) NOT NULL, ename varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, job varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, mgr int(4) NULL DEFAULT …

UML类图的6种关系

目录 一、UML类图的6种关系&#xff08;依赖关系由弱到强&#xff09;&#xff1a; 二、6种关系归纳总结 2.1 第一种归纳方式&#xff1a;先分组&#xff0c;再分组&#xff08;由大到小&#xff0c;由宏观到微观&#xff09; 2.2 第二种归纳方式&#xff1a;先聚合&#x…

计算机毕业论文内容参考|基于微信小程序和云开发的小区垃圾分类知识手册平台的设计与实现

文章目录 导文摘要前言绪论1课题背景2国内外现状与趋势相关技术与方法介绍系统分析总结与展望1本文总结2后续工作展望导文 计算机毕业论文内容参考|基于微信小程序和云开发的小区垃圾分类知识手册平台的设计与实现 摘要 本文介绍了基于微信小程序和云开发的小区垃圾分类知识手…

网络编程1—— IP地址 + 端口号 +TCP/IP协议 + 协议分层的封装与应用

文章目录 前言一、网络发展各阶段二、网络通信的三大要素1.IP地址2.端口号3.网络协议 三、TCP/IP五层网络模型各层级的用处网络设备所在分层 四、封装和分用封装分用网络传输的实际情况 总结 前言 本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各…

Flutter流式组件Wrap

Wrap组件类似Row组件都是横向依次排列&#xff0c;唯一的区别就是Wrap能自动换行。 主要代码&#xff1a; Wrap(spacing: 10, //左右间距runSpacing: 10, //上下间距// direction: Axis.vertical,//主轴的方向&#xff0c;默认横向// alignment: WrapAlignment.spaceBetween, …

React hooks之useCallback的使用与性能分析

使用useCallback优化代码 useCallback是对传过来的回调函数优化&#xff0c;返回的是一个函数&#xff1b;useMemo返回值可以是任何&#xff0c;函数&#xff0c;对象等都可以。 简单来说就是返回一个函数&#xff0c;只有在依赖项发生变化的时候才会更新&#xff08;返回一个…

《项目实战》构建SpringCloud alibaba项目(二、构建微服务鉴权子工程store-authority-service)

系列文章目录 构建SpringCloud alibaba项目&#xff08;一、构建父工程、公共库、网关&#xff09; 构建SpringCloud alibaba项目&#xff08;二、构建微服务鉴权子工程store-authority-service&#xff09; 文章目录 系列文章目录前言1、在公共库增加 UserInfo类2、微服务鉴权…

电脑文件怎么加密?哪个文件加密软件好用?

不少人的电脑中都存放着一些重要文件&#xff0c;这些文件需要使用专业的方式进行加密保护。那么电脑文件该怎么加密呢&#xff1f;下面我们就通过本文来一起了解一下吧。 超级加密3000 作为一款备受好评的文件加密软件&#xff0c;超级加密3000在安全性、便捷性、全面性等方面…

记录征战Mini开发板从无到有

前言 我们店铺的开发板目前主要有Altera,Xilinx以及国产安路&#xff0c;高云。Xilinx只有Spartan6系列&#xff0c;这个系列的芯片只支持ISE软件&#xff0c;但是很多客户用的是VIVADO软件&#xff0c;所以导致我们无法满足客户的需求。基于此原因&#xff0c;我们经过几个月…

AST-抽象语法树

js加密解混淆首先想到的是AST语法树&#xff0c;那么什么是AST呢&#xff0c;学习AST过程的一些笔记 1.AST是JS执行的第一步是读取 js 文件中的字符流&#xff0c;然后通过词法分析生成令牌流Tokens&#xff0c;之后再通过语法分析生成 AST&#xff08;Abstract Syntax Tree&a…