使用 JDK 8 编译但针对 JRE 7 的 ConcurrentHashMap 崩溃应用程序
ConcurrentHashMap crashing application compiled with JDK 8 but targeting JRE 7
我 运行 今天遇到了一个非常意外的错误,虽然我能够找到解决整个问题的方法,但我不确定我是否完全理解它为什么会这样做。
我正在使用的代码最初是用 JDK 7 环境编写的,当然是针对 JRE 7 的。在代码中,我使用的是 ConcurrentHashMap
并且需要迭代中的键地图。为此,我使用了 map.keySet()
,根据 JavaDocs 应该 return 和 Set<K>
。这工作正常,直到我们的构建环境切换到 JDK8.
当我们移动到 JDK8 时,我确保在调用 javac 时为 1.7 调用 target/source。所以当代码想要遍历地图的键时开始失败时,我感到非常惊讶。没有抛出错误,没有异常,线程只是简单地停止了。在做了一些研究后,我发现 Java8 对 ConcurrentHashMap
的实现 .keySet()
方法 return 是 KeySetView<K,V>
.
我通过从使用 map.keySet()
切换到使用 map.keys()
获得 Enumeration<K>
来解决问题。
现在我对这个问题的猜测是,尽管项目是针对 Java7 编译的,因为使用了 JDK8,但包含了 Java8 库,但为什么不包含它在遇到不匹配时抛出错误或异常?
这里是一个代码片段:
class MapProcessing
{
private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
public MapProcessing()
{
map.put("First",new Object());
map.put("Second",new Object());
map.put("Third",new Object());
}
public void processing()
{
// when calling this type of loop causes a freeze on our system.
for(String key : map.keySet())
{
System.out.println(key);
}
}
public void working()
{
// This is what I had to do to fix the problem.
Enumeration<String> keys = map.keys();
while(keys.hasMoreElements())
{
String key = keys.nextElement();
System.out.println(key);
}
}
}
我们正在使用 Oracle JDK 8 build 40 进行编译,在 Windows 2012 服务器上的 javac 中使用 1.7 的目标和源 1.7。
代码是 运行 在 Windows 2012 服务器上使用 Oracle JVM 7 build 25 运行。
每当您使用针对旧版本的 -source
参数使用较新的 JDK 构建项目时,您将收到此编译器警告:
warning: [options] bootstrap class path not set in conjunction with -source 1.7
This blog entry说说什么意思
基本上,您收到此警告是因为 Java 正在使用较旧的语言规则编译它,但针对较新的 class 库...并且 Java 存在一些兼容性问题8 个版本,因为 Oracle 移动了一些内部 classes。
解决方法是在编译时使用 -bootclasspath
参数将其指向旧版本的 rt.jar
。
如果我用 Java 8 和 javac -source 1.7 -target 1.8 编译你的代码,然后 运行 用 Java 7 编译你的代码,我得到一个
Exception in thread "main" java.lang.NoSuchMethodError:
java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
at Whosebugt.Test.processing(Test.java:20)
at Whosebugt.Test.main(Test.java:27)
这是因为字节码看起来像
public void processing();
Code:
0: aload_0
1: getfield #4 // Field map:Ljava/util/concurrent/ConcurrentHashMap;
4: invokevirtual #10 // Method java/util/concurrent/ConcurrentHashMap.keySet:()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
7: invokevirtual #11 // Method java/util/concurrent/ConcurrentHashMap$KeySetView.iterator:()Ljava/util/Iterator;
10: astore_1
并明确引用 Java 中不存在的 ConcurrentHashMap$KeySetView。我在 Mac 上 Java 1.7.0_79 和 1.8.0_45
如果将代码更改为(仅使用地图界面):
private Map<String, Object> map = new ConcurrentHashMap<String, Object>();
那么它对我有用。字节码看起来像
public void processing();
Code:
0: aload_0
1: getfield #4 // Field map:Ljava/util/Map;
4: invokeinterface #10, 1 // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
9: invokeinterface #11, 1 // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
14: astore_1
我 运行 今天遇到了一个非常意外的错误,虽然我能够找到解决整个问题的方法,但我不确定我是否完全理解它为什么会这样做。
我正在使用的代码最初是用 JDK 7 环境编写的,当然是针对 JRE 7 的。在代码中,我使用的是 ConcurrentHashMap
并且需要迭代中的键地图。为此,我使用了 map.keySet()
,根据 JavaDocs 应该 return 和 Set<K>
。这工作正常,直到我们的构建环境切换到 JDK8.
当我们移动到 JDK8 时,我确保在调用 javac 时为 1.7 调用 target/source。所以当代码想要遍历地图的键时开始失败时,我感到非常惊讶。没有抛出错误,没有异常,线程只是简单地停止了。在做了一些研究后,我发现 Java8 对 ConcurrentHashMap
的实现 .keySet()
方法 return 是 KeySetView<K,V>
.
我通过从使用 map.keySet()
切换到使用 map.keys()
获得 Enumeration<K>
来解决问题。
现在我对这个问题的猜测是,尽管项目是针对 Java7 编译的,因为使用了 JDK8,但包含了 Java8 库,但为什么不包含它在遇到不匹配时抛出错误或异常?
这里是一个代码片段:
class MapProcessing
{
private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
public MapProcessing()
{
map.put("First",new Object());
map.put("Second",new Object());
map.put("Third",new Object());
}
public void processing()
{
// when calling this type of loop causes a freeze on our system.
for(String key : map.keySet())
{
System.out.println(key);
}
}
public void working()
{
// This is what I had to do to fix the problem.
Enumeration<String> keys = map.keys();
while(keys.hasMoreElements())
{
String key = keys.nextElement();
System.out.println(key);
}
}
}
我们正在使用 Oracle JDK 8 build 40 进行编译,在 Windows 2012 服务器上的 javac 中使用 1.7 的目标和源 1.7。
代码是 运行 在 Windows 2012 服务器上使用 Oracle JVM 7 build 25 运行。
每当您使用针对旧版本的 -source
参数使用较新的 JDK 构建项目时,您将收到此编译器警告:
warning: [options] bootstrap class path not set in conjunction with -source 1.7
This blog entry说说什么意思
基本上,您收到此警告是因为 Java 正在使用较旧的语言规则编译它,但针对较新的 class 库...并且 Java 存在一些兼容性问题8 个版本,因为 Oracle 移动了一些内部 classes。
解决方法是在编译时使用 -bootclasspath
参数将其指向旧版本的 rt.jar
。
如果我用 Java 8 和 javac -source 1.7 -target 1.8 编译你的代码,然后 运行 用 Java 7 编译你的代码,我得到一个
Exception in thread "main" java.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView; at Whosebugt.Test.processing(Test.java:20) at Whosebugt.Test.main(Test.java:27)
这是因为字节码看起来像
public void processing(); Code: 0: aload_0 1: getfield #4 // Field map:Ljava/util/concurrent/ConcurrentHashMap; 4: invokevirtual #10 // Method java/util/concurrent/ConcurrentHashMap.keySet:()Ljava/util/concurrent/ConcurrentHashMap$KeySetView; 7: invokevirtual #11 // Method java/util/concurrent/ConcurrentHashMap$KeySetView.iterator:()Ljava/util/Iterator; 10: astore_1
并明确引用 Java 中不存在的 ConcurrentHashMap$KeySetView。我在 Mac 上 Java 1.7.0_79 和 1.8.0_45
如果将代码更改为(仅使用地图界面):
private Map<String, Object> map = new ConcurrentHashMap<String, Object>();
那么它对我有用。字节码看起来像
public void processing(); Code: 0: aload_0 1: getfield #4 // Field map:Ljava/util/Map; 4: invokeinterface #10, 1 // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set; 9: invokeinterface #11, 1 // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator; 14: astore_1