其他

在 Java 9 里对 IntegerCache 进行修改?

2017-05-30 OSC-协作翻译 开源中国


协作翻译

原文:Hacking the IntegerCache in Java 9?

链接:https://dzone.com/articles/hacking-the-integercache-in-java-9

译者:-_-struggle, Tocy, 无若, cassia_tora


五年前,我在 Hungarian 上发表了一篇关于“怎样在 JDK 中修改 IntegerCahe”的文章。侵入 Java 运行时是非常有必要的,而且有显而易见的好处。当你在编写侵入代码时,你会对反射的工作机制以及 Integer 类的实现过程有一个更加深入的理解。


Integer 类有一个私有嵌套类——IntegerCache,包含 int 值从 -127 到 128 的 Integer 对象。当代码要把一个 int 数据类型封装成 Integer 对象,并且 int 值在范围 -127~128 内时,Java 运行时环境使用缓存而不是重新创建一个 Integer 对象。这是为了加快代码优化的速度,要记住:编程中的 int 数据类型在很多情况下是在规定的范围内的(类似数组的索引)。


这样做的副作用是,在很多时候,使用等号运算符来比较两个整数对象可能会工作,只要整数值在该范围内。这通常发生在单元测试期间。 在操作模式下,当某些值大于 128 时,此代码执行失败。


使用反射对 IntegerCache 做修改也可能导致奇怪的副作用,而且这会对整个 JVM 产生影响。如果一个 servlet 重新定义了小整数的缓存值,那么在同一个 JVM 上的相同 Tomcat 中运行的所有其他 servlet 都将受到影响。


现在我已经上手了 Java 9 的早期公开版本,我想到可以用新版的 Java 来做相同的侵入操作。在开始用 Java 9 侵入之前,我们先用 Java 8 做一次。

import java.lang.reflect.Field;

import java.util.Random;


public class Entropy {

 public static void main(String[] args)

    throws Exception {

    

        // Extract the IntegerCache through reflection

        //获取类

        Class < ? > clazz = Class.forName(

            "java.lang.Integer$IntegerCache");


        //获取cache成员变量

        Field field = clazz.getDeclaredField("cache");

        field.setAccessible(true);

        Integer[] cache = (Integer[]) field.get(clazz);

        

        // Rewrite the Integer cache

        for (int i = 0; i < cache.length; i++) {

            cache[i] = new Integer(

                new Random().nextInt(cache.length));

        }

        

        // Prove randomness

        for (int i = 0; i < 10; i++) {

            System.out.println((Integer) i);

        }

    }



}


上面的代码通过反射获取了 IntegerCache,然后把随机数赋值给 cache 成员变量。真调皮!


我们在 Java 9 中也能执行这些代码。但是,不要高兴得太早。当人们试图在 Java 9 中进行违规操作时,后果会更严重。

Exception in thread "main" java.lang.reflect.InaccessibleObjectException:

  Unable to make field static final java.lang.Integer[]

  java.lang.Integer$IntegerCache.cache

  accessible: module java.base does not "opens java.lang" to unnamed module @1bc6a36e


会抛出一个 Java 8 中没有的异常。说是"对象不可访问",因为 java.base 模块(每个 java 程序都会自动导入的 JDK 运行时的一部分)不会将模块打开(侵入)到未知的模块。当我们试图设置该成员变量(cache)为“可访问的”的时候,就会抛出这个异常。


在 Java 8 中轻易就能访问到的对象在 Java 9 中变得不可访问了,因为 Java 9 中模块系统会对对象进行保护。代码只有在类处于同一模块下,或为了所有模块或本模块能进行反射性访问,模块打开包时,才能访问成员变量、方法以及其他内容。这些都是在"module-info.java"模块定义文件里配置的:

module myModule {

    exports com.javax0.module.demo;

    opens com.javax0.module.demo;

}


“java.base”模块不会为了编程人员开放它本身,尤其是不会为了未知的模块——也就是我们自己运行的代码。如果我们为了自己的代码创建一个模块,并且对它进行命名,然后错误信息就会包括我们模块的名字(而不是说未知的模块)。


那我们是不是可以用 Java 9 本身有的方法来达到我们的目的呢?在“java.lang.reflect.Module”模块有一个"addOpens"方法可以做到。



这样做可以吗?


样做是不行的,对于我们这些想这样做的人来说是个坏消息。如果包已经被模块通过上面的方法打开了,那么我们只能将模块下的包打开到别的模块。这样的话,模块可以传递给其他模块,虽然 Java 9 不对外开放的内容我们还是无法访问到 ,但是通过反射模块已经可以访问到一些包了。


但是同时,对于我们来说这也是一个好消息。不像 Java 8,Java 9 的安全性比较好,不容易被攻击。至少这个小漏洞被堵住了。看起来 Java 好像开始变成专业级别的,而不再是一个小玩物了(不好意思,开个玩笑)。在不久的将来,我们可以从 RPG 和 COBOL 迁移一些重要的程序到 Java 上来。



推荐阅读

盘点那些评分最高的项目管理工具,不服来战!

Redis 单例、主从模式、sentinel 以及集群的配置方式及优缺点对比

Spring 思维导图,让 Spring 不再难懂(ioc 篇)

一名 40 岁“老”程序员的反思

“放码过来”邀您亮“项”,一不小心就火了!

点击“阅读原文”查看更多精彩内容

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存