07 运行时常量池索引的 rewrite

2024-05-28 15:32
文章标签 运行 索引 常量 07 rewrite

本文主要是介绍07 运行时常量池索引的 rewrite,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

如果您看过这篇文章的话, 57 相互引用的类加载的调试, 那么还记得文章最末尾留下来的问题么 ? 

细心的你, 可能会发现 code 运行时的字节码 和 class 文件中的字节码有一些不一样的地方 ?

发现没得, getstatic, putstatic, invokevirtual 的操作数运行时 和 class文件中 是不一样的, 这是咋回事呢, 这个是 这个index是在Rewriter阶段重写过的(这里的细节与本文没有关系, 暂时不展开) 

那么 我们今天 就来看一下这个问题, 呵呵 究竟是哪里重写了 这些索引 

以下代码, 截图 基于 jdk9 

测试用例

package com.hx.test04;/*** LoadRefEachOther** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2020-03-08 10:58*/
public class Test06LoanRefEachOther {// Test06LoanRefEachOther// refer : https://hllvm-group.iteye.com/group/topic/38847public static void main(String[] args) {System.out.println(Clazz1.x1);
//    System.out.println(Clazz2.x1);System.out.println(Clazz2.x2);}/*** Clazz1** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2020-03-08 10:59*/private static class Clazz1 {static int x1 = 1;int f01;int f02;int f03;int f04;int f05;static {x1 = Clazz2.x2;}}/*** Clazz2** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2020-03-08 11:00*/private static class Clazz2 extends Clazz1 {static int x2 = 2;int f01;int f02;int f03;int f04;int f05;static {x2 = Clazz1.x1;}}}

引用一下相关字节码 信息 

 Test06LoanRefEachOther$Clazz1.<clinit>

Clazz1static {};Code:0: iconst_11: putstatic     #3                  // Field x1:I4: getstatic     #4                  // Field com/hx/test04/Test06LoanRefEachOther$Clazz2.x2:I7: putstatic     #3                  // Field x1:I10: return

运行时 Clazz1.<clinit>方法 code 的相关信息 

(lldb) print method->code_base()
(address) $247 = 0x00000001042cc480 "\x04\xffffffb3\x02"
(lldb) print method->code_size()
(int) $248 = 11
(lldb) x 0x00000001042cc480
0x1042cc480: 04 b3 02 00 b2 03 00 b3 02 00 b1 ff 00 3c ff 08  .............<..
0x1042cc490: 10 31 00 00 00 00 00 00 40 6f 43 03 01 00 00 00  .1......@oC.....# code 拆解如下
0x1042cc480: 04: iconst_1
0x1042cc481: b3 02 00: putstatic #2 // Field x1:I
0x1042cc484: b2 03 00: getstatic #3 // Field com/hx/test04/Test06LoanRefEachOther$Clazz2.x2:I
0x1042cc487: b3 02 00: putstatic #2 // Field x1:I
0x1042cc48a: b1: return

rewriter.scan_method ? 

在 rewriter.scan_method 里面打一个断点 

首先是 rewrite 的步骤是在 类加载的 link 阶段 

我们发现 当前扫描的方法 code_base 为 0x10a4cc480, 当前 code 为 0x10a4cc481 

0x10a4cc480 处为 iconst_1, 断点处扫描到了 "putstatic     #3" 

进入 rewriter. rewrite_member_reference 方法, 我们发现 原来的操作数 3, 换成了 cache_index : 2 

内存中的变化情况如下 

(lldb) x 0x10a4cc480
0x10a4cc480: 04 b3 00 03 b2 00 04 b3 00 03 b1 ff 00 3e ff 08  .............>..
0x10a4cc490: 10 31 00 00 00 00 00 00 40 6f 63 09 01 00 00 00  .1......@oc.....
(lldb) x 0x10a4cc480
0x10a4cc480: 04 b3 02 00 b2 00 04 b3 00 03 b1 ff 00 3e ff 08  .............>..
0x10a4cc490: 10 31 00 00 00 00 00 00 40 6f 63 09 01 00 00 00  .1......@oc.....

可以看到 0x10a4cc482 开始两个字节, 00 03 换成 00 02 

可以看到两点变化, 第一个是 操作数本身逻辑值发生了变化, 另外一点是 存储方式由 大端序 变成了 小端序 

当这个方法的所有的字节码扫描完了之后, 就完成了 class 文件中的 code 到 内存中的 code 的转变了 

呵呵 这个常量池索引 会影响到那些 字节码 呢? 

        case Bytecodes::_putstatic      :case Bytecodes::_putfield       : {// 省略了一部分和我们这里无关的代码// fall throughcase Bytecodes::_getstatic      : // fall throughcase Bytecodes::_getfield       : // fall throughcase Bytecodes::_invokevirtual  : // fall throughcase Bytecodes::_invokestatic   :case Bytecodes::_invokeinterface:case Bytecodes::_invokehandle   : // if reverse=truerewrite_member_reference(bcp, prefix_length+1, reverse);break;

rewriter.compute_index_maps

上面还有一个问题, 转换前后的索引有什么规则呢 ? 那我们接着往下看 

int  cp_entry_to_cp_cache(int i) { assert(has_cp_cache(i), "oob"); return _cp_map.at(i); }

老索引 -> 新索引是来自于 这个 _cp_map 

_cp_map, _cp_cache_map 的计算来自于 rewriter.compute_index_maps 

前者为 老索引 -> 新索引, 后者为 新索引 -> 老索引 

这里将 JVM_CONSTANT_InterfaceMethodref, JVM_CONSTANT_Fieldref, JVM_CONSTANT_Methodref 重新排列, 构建了索引 

[add at 2021.11.21]将 JVM_CONSTANT_String, JVM_CONSTANT_MethodHandle, JVM_CONSTANT_MethodType 重新排列, 构建了索引 

Test06LoanRefEachOther$Clazz1 的常量池如下 

/Users/jerry/ClionProjects/HelloOpenJdk/jdk9/build/macosx-x86_64-normal-serverANDclient-slowdebug/jdk/bin/java -da -dsa -Xint -Xmx10M -XX:+UseSerialGC com.hx.test04.Test06LoanRefEachOther
Signal: SIGSEGV (signal SIGSEGV)
Signal: SIGSEGV (signal SIGSEGV)
{constant pool}- holder: 0x00000007c008fe28- cache: 0x0000000000000000- resolved_references: 0x0000000000000000- reference_map: 0x0000000000000000-   1 : Method : klass_index=5 name_and_type_index=30-   2 : Method : klass_index=6 name_and_type_index=30-   3 : Field : klass_index=5 name_and_type_index=31-   4 : Field : klass_index=32 name_and_type_index=33-   5 : Unresolved Class : 'com/hx/test04/Test06LoanRefEachOther$Clazz1'-   6 : Unresolved Class : 'java/lang/Object'-   7 : Utf8 : 'x1'-   8 : Utf8 : 'I'-   9 : Utf8 : 'f01'-  10 : Utf8 : 'f02'-  11 : Utf8 : 'f03'-  12 : Utf8 : 'f04'-  13 : Utf8 : 'f05'-  14 : Utf8 : '<init>'-  15 : Utf8 : '()V'-  16 : Utf8 : 'Code'-  17 : Utf8 : 'LineNumberTable'-  18 : Utf8 : 'LocalVariableTable'-  19 : Utf8 : 'this'-  20 : Utf8 : 'Clazz1'-  21 : Utf8 : 'InnerClasses'-  22 : Utf8 : 'Lcom/hx/test04/Test06LoanRefEachOther$Clazz1;'-  23 : Unresolved Class : 'com/hx/test04/Test06LoanRefEachOther$1'-  24 : Utf8 : '(Lcom/hx/test04/Test06LoanRefEachOther$1;)V'-  25 : Utf8 : 'x0'-  26 : Utf8 : 'Lcom/hx/test04/Test06LoanRefEachOther$1;'-  27 : Utf8 : '<clinit>'-  28 : Utf8 : 'SourceFile'-  29 : Utf8 : 'Test06LoanRefEachOther.java'-  30 : NameAndType : name_index=14 signature_index=15-  31 : NameAndType : name_index=7 signature_index=8-  32 : Unresolved Class : 'com/hx/test04/Test06LoanRefEachOther$Clazz2'-  33 : NameAndType : name_index=40 signature_index=8-  34 : Unresolved Class : 'com/hx/test04/Test06LoanRefEachOther'-  35 : Utf8 : 'com/hx/test04/Test06LoanRefEachOther$Clazz1'-  36 : Utf8 : 'java/lang/Object'-  37 : Utf8 : 'com/hx/test04/Test06LoanRefEachOther$1'-  38 : Utf8 : 'com/hx/test04/Test06LoanRefEachOther$Clazz2'-  39 : Utf8 : 'Clazz2'-  40 : Utf8 : 'x2'-  41 : Utf8 : 'com/hx/test04/Test06LoanRefEachOther'

可以发现 只有两个 Field, 两个 Method, 所以 图中 新索引 -> 老索引 的数量为 4 

因为新索引从 0 开始, 索引 新旧索引偏移 -1 

片尾彩蛋

如果配置了 RegisterFinalizersAtInit, 那么在 Object() 的构造方法后面 把 Bytecodes::_return 更新成了 Bytecodes::_return_register_finalizer

那么配置了 RegisterFinalizersAtInit 为 false 的情况, finalizer 在哪里注册呢 ? 

在为对象分配空间的时候 

HSDB 拿到的是 rewire 之前的字节码, 因此 RegisterFinalizersAtInit 为 true 只能在运行时验证了 

(lldb) x 0x10dcc25a8
0x10dcc25a8: b1 ff 00 64 00 00 00 00 00 00 00 00 01 00 18 00  ...d............
0x10dcc25b8: 19 00 00 00 00 00 01 00 20 26 cc 0d 01 00 00 00  ........ &......
(lldb) x 0x10dcc25a8
0x10dcc25a8: e8 ff 00 64 00 00 00 00 00 00 00 00 01 00 18 00  ...d............
0x10dcc25b8: 19 00 00 00 00 00 01 00 20 26 cc 0d 01 00 00 00  ........ &......

======================= add at 2020.05.03 =======================

呵呵 找到了一个之前的 todo, 链接一下 

R大 曾经 在这篇文章中 Java 8下如何查看JVM里Java应用的字节码? 介绍了 常量池索引  Rewrite 的相关只是 

有些字节码指令的操作数在Class文件里跟在运行时看起来不同,是因为HotSpot VM在加载类的时候会对字节码做改写,把某些指令的操作数从常量池下标(constant pool index)改写为常量池缓存下标(constant pool cache index)。这是因为这些指令所需要引用的信息比一个constant pool entry slot要大,需要另外开一个更大的数据结构来放该常量池项的内容。

完 

参考

57 相互引用的类加载的调试

Java 8下如何查看JVM里Java应用的字节码?

这篇关于07 运行时常量池索引的 rewrite的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1010966

相关文章

oracle数据库索引失效的问题及解决

《oracle数据库索引失效的问题及解决》本文总结了在Oracle数据库中索引失效的一些常见场景,包括使用isnull、isnotnull、!=、、、函数处理、like前置%查询以及范围索引和等值索引... 目录oracle数据库索引失效问题场景环境索引失效情况及验证结论一结论二结论三结论四结论五总结ora

Linux使用nohup命令在后台运行脚本

《Linux使用nohup命令在后台运行脚本》在Linux或类Unix系统中,后台运行脚本是一项非常实用的技能,尤其适用于需要长时间运行的任务或服务,本文我们来看看如何使用nohup命令在后台... 目录nohup 命令简介基本用法输出重定向& 符号的作用后台进程的特点注意事项实际应用场景长时间运行的任务服

如何在一台服务器上使用docker运行kafka集群

《如何在一台服务器上使用docker运行kafka集群》文章详细介绍了如何在一台服务器上使用Docker运行Kafka集群,包括拉取镜像、创建网络、启动Kafka容器、检查运行状态、编写启动和关闭脚本... 目录1.拉取镜像2.创建集群之间通信的网络3.将zookeeper加入到网络中4.启动kafka集群

Python中列表的高级索引技巧分享

《Python中列表的高级索引技巧分享》列表是Python中最常用的数据结构之一,它允许你存储多个元素,并且可以通过索引来访问这些元素,本文将带你深入了解Python列表的高级索引技巧,希望对... 目录1.基本索引2.切片3.负数索引切片4.步长5.多维列表6.列表解析7.切片赋值8.删除元素9.反转列表

MySQL的索引失效的原因实例及解决方案

《MySQL的索引失效的原因实例及解决方案》这篇文章主要讨论了MySQL索引失效的常见原因及其解决方案,它涵盖了数据类型不匹配、隐式转换、函数或表达式、范围查询、LIKE查询、OR条件、全表扫描、索引... 目录1. 数据类型不匹配2. 隐式转换3. 函数或表达式4. 范围查询之后的列5. like 查询6

PostgreSQL如何查询表结构和索引信息

《PostgreSQL如何查询表结构和索引信息》文章介绍了在PostgreSQL中查询表结构和索引信息的几种方法,包括使用`d`元命令、系统数据字典查询以及使用可视化工具DBeaver... 目录前言使用\d元命令查看表字段信息和索引信息通过系统数据字典查询表结构通过系统数据字典查询索引信息查询所有的表名可

PostgreSQL如何用psql运行SQL文件

《PostgreSQL如何用psql运行SQL文件》文章介绍了两种运行预写好的SQL文件的方式:首先连接数据库后执行,或者直接通过psql命令执行,需要注意的是,文件路径在Linux系统中应使用斜杠/... 目录PostgreSQ编程L用psql运行SQL文件方式一方式二总结PostgreSQL用psql运

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用