Java 9中如何对IntegerCache进行修改详解
在开始本文的正文之前,我们下面来看看下面这段代码:
Java中Integer类的IntegerCache的作用
包名:java.lang
文件名:Integer.java
方法名:IntegerCache
方法的代码如下:
privatestaticclassIntegerCache{ staticfinalinthigh; staticfinalIntegercache[]; static{ finalintlow=-128; //highvaluemaybeconfiguredbyproperty inth=127; if(integerCacheHighPropValue!=null){ //UseLong.decodeheretoavoidinvokingmethodsthat //requireInteger'sautoboxingcachetobeinitialized inti=Long.decode(integerCacheHighPropValue).intValue(); i=Math.max(i,127); //MaximumarraysizeisInteger.MAX_VALUE h=Math.min(i,Integer.MAX_VALUE--low); } high=h; cache=newInteger[(high-low)+1]; intj=low; for(intk=0;k我们在代码中看到,low为-128,high为127,这样的话,在Java编程中,如果要使用-128——127这个区间的对象的话,是直接使用这个Cache中的对象的。
上面是段简单的介绍,帮助大家理解下IntegerCache,下面开始本文的正文:
引言
5年前,我在Hungarian上发表了一篇关于JDK中如何改变IntegerCache的文章。这种做法其实是深入进Java运行时,在实际并没有使用的场景。当你开发这些研究代码时,你才能更好的理解反射是如何工作的,以及Integer类是如何实现的。
Integer类有一个私有的嵌套内,名为IntegerCache,包含了值从-127到128的Integer对象。
当代码需要从int类型封箱成Integer对象,而且值在这个范围内时,那么Java运行时会使用这个缓存,而不是创建一个新的Integer对象。这主要是处于性能优化的考虑,我们必须牢记在心的是很多int值在程序中很多时候都处于这个范围内(例如数组的下标索引)。
这样做的副作用是,很多时候,使用等号操作符来比较两个Integer对象时,只要值在范围内都是有效的。这在单元测试中很典型。而在运行模式下,当数值大于128时,代码执行会失败。
使用反射来访问IntegerCache类时会导致一些奇怪的副作用,注意这会影响到整个的JVM。如果一个Servlet重新定义了小的Integer缓存值,那么所有运行在同一个Tomcat下的其他Servlet也遭遇同样问题。
在LukasEder和Sitepoint上面还有其他一些文章描述此问题。
现在我已经开始在玩弄Java9的早期发布版本,在我脑海里我一直要做的就是对新的Java版本进行各种实验。在开始之前,让我们先看看在Java8中的做法。
在Lukas的文章中,我将他的示例代码贴在此处:
importjava.lang.reflect.Field; importjava.util.Random; publicclassEntropy{ publicstaticvoidmain(String[]args) throwsException{ //ExtracttheIntegerCachethroughreflection Class<>clazz=Class.forName( "java.lang.Integer$IntegerCache"); Fieldfield=clazz.getDeclaredField("cache"); field.setAccessible(true); Integer[]cache=(Integer[])field.get(clazz); //RewritetheIntegercache for(inti=0;i此代码通过反射方式访问IntegerCache,然后使用随机值对缓存进行填充(淘气!)。
我们尝试在Java9中执行相同的代码,别指望有什么乐趣。当有人尝试违反它时会发现Java9的限制更加严格。
Exceptioninthread"main"java.lang.reflect.InaccessibleObjectException: Unabletomakefieldstaticfinaljava.lang.Integer[] java.lang.Integer$IntegerCache.cache accessible:modulejava.basedoesnot"opensjava.lang"tounnamedmodule@1bc6a36e程序抛出了异常,这个异常在Java8中是不会发生的。相当于说对象是不可范文的,因为java.base模块的原因,这是JDK的组成部分,在每个Java程序启动时被自动的导入,不允许打开未命名的模块。这个异常是在当我们尝试设置字段可访问属性时抛出的。
我们在Java8可轻松访问的对象,现在在Java9中不能访问了,因为新的模块系统对此进行了保护。代码只能访问字段、方法和其他用反射能访问的信息,只有当类在相同的模块中,或者模块打开了包用于反射方式访问。这个可以通过 module-info.java模块定义文件来实现:
modulemyModule{ exportscom.javax0.module.demo; openscom.javax0.module.demo; }这个模块java.base不用不用自行打开用于反射访问,特别是未命名模块更不需要。如果我们创建了一个模块并进行命名,那么错误信息将包含模块的名称。
我们能否在程序里打开模块呢?java.lang.reflect.Module模块有一个addOpens的方法可以做到。
可行吗?
对开发者来说坏消息是:不可行。它只能在另外一个模块中打开一个模块中的包,并且包已经在该模块中通过调用这个方法打开过。这种方法只能让模块传递给另外的模块权利,前提是另外的模块已经以某种方式打开过相同的包,而不能打开没有打开过的包(译者注:很难理解,不是吗?)。
但与此同时好消息是:Java9不像Java8那么容易被破解。最少这个漏洞被关闭了。看起来Java开始往专业级发展,而不仅仅是个玩具(译者注:谁说Java是个玩具了?)。不久的将来你可以非常严肃的将RPG和COBOL语言的项目迁移到Java上了。(很抱歉,我开玩笑的)
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。
本文翻译自:https://dzone.com/articles/hacking-the-integercache-in-java-9