从 Hazelcast 反序列化 synchronizedSet 时的 Stackoverflow

问题描述

考虑使用以下代码片段从 Hazelcast 获取/提交 Collections.synchronizedSet:

IMap imap = getIMapFromHazelcastClient();
Set<UUID> mySet = (Set<UUID>)imap.get("someKey"); //This may trigger a stackOverflow,see below.
if ( mySet == null ){
    mySet = Collections.synchronizedSet(new HashSet<UUID>());
}else{
    mySet = Collections.synchronizedSet(mySet);
}

//Concurrently add/remove elements from mySet

//After all concurrent operations complete,store the updated Set to Hazelcast.
//mySet should contain about 1-1.5K UUIDs at this point,every time.
imap.set("someKey",mySet);

代码运行几次后(不确定有多少次或为什么),在尝试从 Hazelcast 获取 Set 时触发以下操作(如上标记):

java.lang.StackOverflowError
at java.base/java.io.ObjectInputStream$PeekInputStream.read(ObjectInputStream.java:2914)
at java.base/java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2930)
at java.base/java.io.ObjectInputStream$BlockDataInputStream.readUnsignedShort(ObjectInputStream.java:3439)
at java.base/java.io.ObjectInputStream$BlockDataInputStream.readUTF(ObjectInputStream.java:3497)
at java.base/java.io.ObjectInputStream.readUTF(ObjectInputStream.java:1242)
at java.base/java.io.ObjectStreamClass.readNonProxy(ObjectStreamClass.java:800)
at java.base/java.io.ObjectInputStream.readClassDescriptor(ObjectInputStream.java:991)
at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2017)
at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1895)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2202)
at java.base/java.io.ObjectInputStream.readobject0(ObjectInputStream.java:1712)
at java.base/java.io.ObjectInputStream.readobject(ObjectInputStream.java:519)
at java.base/java.io.ObjectInputStream.readobject(ObjectInputStream.java:477)
at java.base/java.util.HashSet.readobject(HashSet.java:344)
at java.base/jdk.internal.reflect.GeneratedMethodAccessor564.invoke(UnkNown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at java.base/java.io.ObjectStreamClass.invokeReadobject(ObjectStreamClass.java:1226)
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2401)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235)
at java.base/java.io.ObjectInputStream.readobject0(ObjectInputStream.java:1712)

...About 900 lines of these
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540)
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235)
at java.base/java.io.ObjectInputStream.readobject0(ObjectInputStream.java:1712)

at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540)
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235)
at java.base/java.io.ObjectInputStream.readobject0(ObjectInputStream.java:1712)
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540)
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235)
at java.base/java.io.ObjectInputStream.readobject0(ObjectInputStream.java:1712)
at java.base/java.io.ObjectInputStream.readobject(ObjectInputStream.java:519)
at java.base/java.io.ObjectInputStream.readobject(ObjectInputStream.java:477)
at com.hazelcast.internal.serialization.impl.JavaDefaultSerializers$JavaSerializer.read(JavaDefaultSerializers.java:84)
at com.hazelcast.internal.serialization.impl.JavaDefaultSerializers$JavaSerializer.read(JavaDefaultSerializers.java:77)
at com.hazelcast.internal.serialization.impl.StreamSerializerAdapter.read(StreamSerializerAdapter.java:48)
at com.hazelcast.internal.serialization.impl.AbstractSerializationService.toObject(AbstractSerializationService.java:187)
at com.hazelcast.client.spi.ClientProxy.toObject(ClientProxy.java:102)
at com.hazelcast.client.proxy.ClientMapProxy.get(ClientMapProxy.java:318)
...

如果相关,应用程序和 Hazelcast (v3.12.9) 都在相同的 JVM 版本上运行:OpenJDK 运行时环境(构建 15.0.1+9-18)和相同的硬件。当 Hazelcast 在 v1.8.0 JVM 上运行时观察到相同的行为。

在存储部分用简单的HashSet替换synchronizedSet后问题似乎解决了:

imap.set("someKey",new HashSet(mySet));

显然,在 Hazelcast 中存储 synchronizedSet 没有意义,但是 stackoverflow 是否有道理?我错过了什么吗?

解决方法

在使用 Hazelcast 4.2-BETA-1 之前,这不会为我重新创建完整的代码。这与您的设置有何不同?

    public static void main(String[] args) throws Exception {
        String mapName = "so66579138";
        String key = "k";

        Config config = new Config();
        config.getNetworkConfig().getJoin().getAutoDetectionConfig().setEnabled(false);
        config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);

        HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(config);
        
        IMap<String,Set<UUID>> imap = hazelcast.getMap(mapName);
        
        Set<UUID> mySet = Collections.synchronizedSet(new HashSet<UUID>());
        for (int i = 0 ; i < 1500 ; i++) {
            mySet.add(UUID.randomUUID());
        }

        // PUT
        imap.set(key,mySet);
        
        // GET
        Set<UUID> mySet2 = imap.get(key);
        System.out.println("mySet2.size() == " + mySet2.size());
        System.out.println("mySet2.getClass().getName() == " + mySet2.getClass().getName());
        
        hazelcast.shutdown();
    }