Java反射机制清空字符串导致业务异常分析
摘要:笔者在处理业务线问题时遇到接口返回的内容和实际内容不一致的现象。
本文分享自华为云社区《Java反射机制清空字符串导致业务异常分析》,作者:毕昇小助手。
编者按:笔者在处理业务线问题时遇到接口返回的内容和实际内容不一致的现象。根因是业务方通过Java反射机制将String类型敏感数据引用的value数组元素全部设置为’0’,从而实现清空用户敏感数据的功能。这种清空用户敏感数据的方法会将字符串常量池相应地址的内容修改,进而导致所有指向该地址的引用的内容和实际值不一致的现象。
背景知识
JVM为了提高性能和减少内存开销,在实例化字符串常量时进行了优化。JVM在Java堆上开辟了一个字符串常量池空间(StringTable),JVM通过ldc指令加载字符串常量时会调用 StringTable::intern 函数将字符串加入到字符串常量池中。
StringTable::intern函数代码
oop StringTable::intern(Handle string_or_null, jchar* name,
int len, TRAPS) {
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);
// Found
if (found_string != NULL) {
ensure_string_alive(found_string);
return found_string;
}
debug_only(StableMemoryChecker smc(name, len * sizeof(name[0])));
assert(!Universe::heap()->is_in_reserved(name),
"proposed name of symbol must be stable");
Handle string;
// try to reuse the string if possible
if (!string_or_null.is_null()) {
string = string_or_null;
} else {
string = java_lang_String::create_from_unicode(name, len, CHECK_NULL);
}
#if INCLUDE_ALL_GCS
if (G1StringDedup::is_enabled()) {
// Deduplicate the string before it is interned. Note that we should never
// deduplicate a string after it has been interned. Doing so will counteract
// compiler optimizations done on e.g. interned string literals.
G1StringDedup::deduplicate(string());
}
#endif
// Grab the StringTable_lock before getting the_table() because it could
// change at safepoint.
oop added_or_found;
{
MutexLocker ml(StringTable_lock, THREAD);
// Otherwise, add to symbol to table
added_or_found = the_table()->basic_add(index, string, name, len,
hashValue, CHECK_NULL);
}
ensure_string_alive(added_or_found);
return added_or_found;
}
StringTable::intern 函数处理流程

字符串的创建方式
根据StringTable::intern函数处理流程,我们可以简单描绘如下6种常见的字符串的创建方式以及引用关系。

现象
某业务线使用fastjson实现Java对象序列化功能,低概率出现接口返回的JSON数据的某个属性值和实际值不一致的现象。正确的属性值应该为"null",实际属性值却为"0000"。
原因分析
为了排除fastjson自身的嫌疑,我们将其替换jackson后,依然会低概率出现同样的现象。由于两个不同三方件同时存在这个问题的可能性不大,为此我们暂时排除fastjson引入该问题的可能性。为了找到该问题的根因,我们在环境中开启远程调试功能。待问题复现,调试代码时我们发现只要是指向"null"的引用,显示的内容全部变成"0000",由此我们初步怀疑字符串常量池中的"null"被修改成"0000"。
一般导致常量池被修改有两种可能性:
- 第三方动态库引入的bug导致字符串常量池内容被修改;
- 在业务代码中通过Java反射机制主动修改字符串常量池内容;
业务方排查项目中使用到的第三方动态库,未发现可疑的动态库,排除第一种可能性。排查业务代码中使用到Java反射的功能,发现清空密码功能会使用到Java反射机制,并且将String类型密码的value数组元素全部设置为’0’。
业务出现的现象可以简单通过代码模拟:
- 在TestString对象类中定义一个nullStr属性,初始值为"null";
- 定义一个带有password属性的User类;
- 在main方法中创建一个密码为"null"的User对象,使用Java反射机制将密码字符串的所有字符全部修改为’0’,分别在密码修改前后打印TestString对象nullStr属性值;
复现代码
import java.lang.reflect.Field;
import java.util.Arrays;
public class TestString {
private String nullStr = "null";
public String getNullStr() {
return nullStr;
}
static class User {
private final String password;
User(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
}
private static void clearPassword(User user) throws Exception {
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] chars = (char[]) field.get(user.getPassword());
Arrays.fill(chars, '0');
}
public static void main(String[] args) throws Exception {
User user = new User("null");
TestString testString = new TestString();
System.out.println("before clear password >>>>");
System.out.println(" User.password:" + user.getPassword());
System.out.println("TestString.nullStr:" + testString.getNullStr());
System.out.println("--------------------------------");
clearPassword(user);
System.out.println("after clear password >>>>");
System.out.println(" User.password:" + user.getPassword());
System.out.println("TestString.nullStr:" + testString.getNullStr());
}
}
复现代码字符串引用关系如下图所示。

User对象的password属性和TestString的nullStr属性引用都同时指向常量池中的"null"字符串,"null"字符串的value指向 {‘n’,‘u’,‘l’,‘l’} char数组。使用Java反射机制将User对象的password属性引用的value数组全部设置为’0’,导致 TestString的nullStr属性值也变成了 “0000”。
输出结果如下:
before clear password >>>>
User.password:null
TestString.nullStr:null
--------------------------------
after clear password >>>>
User.password:0000
TestString.nullStr:0000
通过输出结果我们可以发现在通过Java反射机制修改某一个字符串内容后,所有指向原字符串的引用的内容全部变成修改后的内容。
总结
在保存业务敏感数据时避免使用String类型保存,建议使用byte[]或char[]数组保存,然后通过Java反射机制清空敏感数据。
Java反射机制清空字符串导致业务异常分析的更多相关文章
- 详解Java反射机制
反射是程序在运行状态下,动态的获取某个类的内部信息的一种操作.例如:类名,包名,所有属性的集合,所有方法的集合,构造方法的集合等.该操作发生在程序的运行时状态,所以编译器管不着有关反射的一些代码,通常 ...
- 一文带你了解Java反射机制
想要获取更多文章可以访问我的博客 - 代码无止境. 上周上班的时候解决一个需求,需要将一批数据导出到Excel.本来公司的中间件组已经封装好了使用POI生成Excel的工具方法,但是无奈产品的需求里面 ...
- day11 Java反射机制
java反射机制 反射是java中的动态机制,它允许我们在程序运行期间再确定类的实例化,方法的调用,属性的调用等,而不是传统意义上的在编码期间确定. 因此,反射可以大大的提高代码的灵活度,但是随之而来 ...
- 长篇图解java反射机制及其应用场景
一.什么是java反射? 在java的面向对象编程过程中,通常我们需要先知道一个Class类,然后new 类名()方式来获取该类的对象.也就是说我们需要在写代码的时候(编译期或者编译期之前)就知道我们 ...
- JAVA反射机制—学习总结
最近收到很多关于Java反射机制的问题留言,其实Java反射机制技术方面没有太多难点,或许是大家在学习过程中遗漏了细小知识点,导致一些问题无法彻底理解,现在我们简单的总结一下,加深印象.什么是反射机制 ...
- 转!!java反射机制
Java 反射机制 基本概念 在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法? 答案是肯定的. 这种动态获取类的信息以及动态调用对象 ...
- Java反射机制(创建Class对象的三种方式)
1:SUN提供的反射机制的类: java.lang.Class<T> java.lang.reflect.Constructor<T> java.lang.reflect.Fi ...
- 【54】Java反射机制剖析
java反射机制: 1.指的是可以于运行时加载,探知和使用编译期间完全未知的类. 2.程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; ...
- 【java】-- java反射机制
参考文章:https://blog.csdn.net/sinat_38259539/article/details/71799078 https://blog.csdn.net/wanderlu ...
- Java反射机制的使用(全)
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6566957.html 一:反射是什么 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有 ...
随机推荐
- Flex 布局项目实战,好像没那么难!
在上篇文章别再用 float 布局了,flex 才是未来!中,我们聊到 Flex 布局才是目前主流的布局方式.在文章最后,我们还贴了一个案例,并且还浅浅地讲解了一下. 有些小伙伴说,这讲解得太粗了,要 ...
- spring-boot集成hikari多数据源
maven依赖 <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</ ...
- vite介绍
什么是 Vite 借用作者的原话: Vite,一个基于浏览器原生 ES imports 的开发服务器.利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用. ...
- Java8新特性(Lambda表达式、Stream流、Optional类)等
1. Lambda表达式由来 1 package java8; 2 3 public class EmployeeTest { 4 public static void main(String[] a ...
- Ubuntu18虚拟机远程开发
Ubuntu18 虚拟机远程开发 1. 安装 VMware 和 Ubuntu18 虚拟机 (1)VMware 官网上下载免费版本 一路 next 安装就行(中间也许需要改一下存放路径) (2)Ubun ...
- 学生开发者勇担青年使命,用AI守护少数人的“视界”
本文分享自华为云社区<[先锋开发者云上说]学生开发者勇担青年使命,用AI守护少数人的"视界">,作者:华为云社区精选 . 青年动人之处,在于他们的勇气,和非凡的创造探索 ...
- iOS程序入口结构
盛年不重来,一日难再晨.及时宜自勉,岁月不待人. 1. 程序入口 在我们开始开发app的时候,第一步往往是通过设置AppDelegate.m的代理方法开始写一些启动的东西,然后再通过控制器View ...
- Spring Boot Bean的多种加载方式
在 Spring Boot 中,您可以以多种方式加载 Bean,这取决于项目的需求和设计.以下是一些常见的加载 Bean 的方式以及相应的示例源代码. 1.组件扫描(Component Scannin ...
- vue 组件之间的自定义方法互相调用
1,先定义一个中间通信文件js globalBus.js import Vue from 'vue'; export const globalBus = new Vue(); 2,A.vue组件的方法 ...
- 全屏API及vue3 hook封装
最近在一个大屏项目遇到一个需求:用户可以通过一个按钮,触发页面部分模块全屏.通过以下API可以实现: Element.requestFullscreen()方法用于发出异步请求使元素进入全屏模式. 且 ...