java.util.UUID interning/recycling

java.util.UUID interning/recycling

UUIDs 在 Java 中是否像字符串一样被实习?如果不是,我是否应该尝试回收 UUID 对象以尽量减少 RAM 使用?

我使用 UUID 作为数据库主键和外键列的数据类型。所以这意味着许多行重复使用 UUID 作为共享外键值。

因此,当从数据库中检索行时,我是否应该检查每个 UUID 是否重复,如果重复,则使用原始对象引用?或者这是否已经代表我完成了,类似于 Strings are interned?

…  // common JDBC code
UUID id = null ;
while (rs.next()) {
    UUID idFresh = rs.getObject( 1 ); 
    // Recycle the UUID object where possible.
    id = ( ( null == id ) || idFresh.equals( id ) ) ? idFresh : id ;  // If null or identical, use the existing object reference.
    String name = rs.getString( 2 );
}
…

快速查看 java runtime source code 表明 UUID 未被保留。

并且实习他们可能不是一个好主意,因为如果你要遍历一个大型数据库,UUID 实习可能会导致 JVM 运行 内存不足只是因为永远不会忘记它的任何 UUID看过。

此外,实习 UUID 也没有太大好处,因为

  • 它们占用的并不多space
    (基本上只是UUID的128位值存储成一对long

  • UUID 比较和哈希码计算很便宜。
    String 实习的最大好处之一是字符串的哈希码只计算一次,这是有点担心,因为它的计算可能有点昂贵。)

UUID(以及字符串)不会自动删除重复数据。一般来说,这也是一个坏主意,因为新创建的 UUID 应该是唯一的,所以共享将不起作用。

当你提到字符串实习时,JVM 确实会在特定情况下共享字符串,例如:

String x = "ab";
String y = "a" + "b";
assert x == y; // references are identical (x and y are shared)

但是,这些是可以在编译时解析的字符串。如果您在运行时创建一个字符串或 UUID,它总是会创建一个新对象。

不过,在您的问题中,您描述了一个不同的场景。在这里,您正在从数据库中读取 UUID。根据数据的不同,可能会有共享 UUID 的好机会,或者可能 none(例如,如果 UUID 用作主键)。

id | name  | country
1  | A     | <UUID-1>
2  | B     | <UUID-1>
3  | C     | <UUID-2>
4  | D     | <UUID-1>
5  | E     | <UUID-1>

(请注意,从数据库或网络读取UUID时,不能假设UUID会被去重。一般情况下,您会收到相同值的副本。)

因此,如果您的数据如上所示,则共享 UUID 很有意义。但它会减少内存使用量吗?

UUID 是具有两个 long 变量的对象。在 64 位 JVM 中,这将占用 32 个字节。如果你共享UUID,那么你只需支付一次32字节,之后只需支付8字节作为参考。如果您使用 compressed pointers,引用将适合 4 个字节。

这个收益够大吗?这取决于您的具体应用。一般来说,我不会共享 UUID。然而,我曾开发过一个应用程序,其中共享 UUID 确实是一种改进。减少内存使用量很关键,从完整对象到引用的减少是一种改进。

话说回来,很少需要这种类型的优化。根据经验,只有当 UUID 被大量共享并且有必要不惜一切代价减少内存时,我才会这样做。否则,CPU 对它们进行重复数据删除的开销和代码中的额外复杂性通常是不值得的,或者更糟的是,甚至可能会减慢您的应用程序。

如果您决定对它们进行重复数据删除,您会怎么做?没有像 String#intern, but you can manually create a map to deduplicate. Depending on whether you want to deduplicate globally or only locally within in the current function call, you can use a ConcurrentHashMap or simply a (non-synchronized) HashMap.

这样的内置函数

作为旁注,与您的问题没有直接关系,我提到了 String#intern,因为它是字符串 API 的一部分。但是,我强烈建议不要使用它,因为它是 a huge performance bottleneck。自己进行重复数据删除会明显更快。