文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

5 年前,我在 Hungarian 上发表了一篇关于 JDK 中如何改变 IntegerCache 的文章。这种做法其实是深入进 Java 运行时,在实际并没有使用的场景。当你开发这些研究代码时,你才能更好的理解反射是如何工作的,以及 Integer 类是如何实现的。

Integer 类有一个私有的嵌套内,名为 IntegerCache ,包含了值从 -127 到 128 的 Integer 对象。当代码需要从 int 类型封箱成 Integer 对象,而且值在这个范围内时,那么 Java 运行时会使用这个缓存,而不是创建一个新的 Integer 对象。这主要是处于性能优化的考虑,我们必须牢记在心的是很多 int 值在程序中很多时候都处于这个范围内(例如数组的下标索引)。

第 1 段(可获 1.64 积分)

这样做的副作用是,很多时候,使用等号操作符来比较两个 Integer 对象时,只要值在范围内都是有效的。这在单元测试中很典型。而在运行模式下,当数值大于 128 时,代码执行会失败。

使用反射来访问 IntegerCache 类时会导致一些奇怪的副作用,注意这会影响到整个的 JVM。如果一个 Servlet 重新定义了小的 Integer 缓存值,那么所有运行在同一个 Tomcat 下的其他 Servlet 也遭遇同样问题。

第 2 段(可获 1.26 积分)

Lukas Eder 和 Sitepoint 上面还有其他一些文章描述此问题。

现在我已经开始在玩弄 Java 9 的早期发布版本,在我脑海里我一直要做的就是对新的 Java 版本进行各种实验。在开始之前,让我们先看看在 Java 8 中的做法。

在 Lukas 的文章中,我将他的示例代码贴在此处:

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");
        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);
        }
    }
}
第 3 段(可获 0.9 积分)

此代码通过反射方式访问 IntegerCache,然后使用随机值对缓存进行填充(淘气!)。

我们尝试在 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 模块的原因,这是 JDK 的组成部分,在每个 Java 程序启动时被自动的导入,不允许打开未命名的模块。这个异常是在当我们尝试设置字段可访问属性时抛出的。

第 4 段(可获 1.4 积分)

我们在 Java 8 可轻松访问的对象,现在在 Java 9 中不能访问了,因为新的模块系统对此进行了保护。代码只能访问字段、方法和其他用反射能访问的信息,只有当类在相同的模块中,或者模块打开了包用于反射方式访问。这个可以通过  module-info.java 模块定义文件来实现:

module myModule {
    exports com.javax0.module.demo;
    opens com.javax0.module.demo;
}

这个模块 java.base 不用不用自行打开用于反射访问,特别是未命名模块更不需要。如果我们创建了一个模块并进行命名,那么错误信息将包含模块的名称。

第 5 段(可获 1.49 积分)

我们能否在程序里打开模块呢? java.lang.reflect.Module 模块有一个 addOpens 的方法可以做到。

可行吗?

对开发者来说坏消息是:不可行。它只能在另外一个模块中打开一个模块中的包,并且包已经在该模块中通过调用这个方法打开过。这种方法只能让模块传递给另外的模块权利,前提是另外的模块已经以某种方式打开过相同的包,而不能打开没有打开过的包(译者注:很难理解,不是吗?)。

但与此同时好消息是:Java 9 不像 Java 8 那么容易被破解。最少这个漏洞被关闭了。看起来 Java 开始往专业级发展,而不仅仅是个玩具(译者注:谁说 Java 是个玩具了?)。不久的将来你可以非常严肃的将 RPG 和 COBOL 语言的项目迁移到 Java 上了。(很抱歉,我开玩笑的)

第 6 段(可获 1.84 积分)

文章评论