JDK-8313678 : SymbolTable can leak Symbols during cleanup
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 17,21,22
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2023-08-03
  • Updated: 2023-09-01
  • Resolved: 2023-08-14
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 17 JDK 21 JDK 22
17.0.9Fixed 21.0.1Fixed 22 b11Fixed
Related Reports
Relates :  
Relates :  
Description
While investigating a memory leak in one of my applications, I created a simple reproducer in which the symbol table grows unbounded.

It is caused by an accidental increment of the Symbol refcount during bucket cleanup - the concurrentHashTable delete_in_bucket routine uses (abuses?) the lookup function given to it, which for symbol table increments the refcount, under the assumption that a successful lookup means a new reference.

This new test case for test/hotspot/gtest/classfile/test_symbolTable.cpp shows the issue succintly:

```
 TEST_VM(SymbolTable, test_cleanup_leak) {
   // Check that dead entry cleanup doesn't increment refcount of live entry in same bucket.

   // Create symbol and release ref, marking it available for cleanup.
   Symbol* entry1 = SymbolTable::new_symbol("hash_collision_123");
   entry1->decrement_refcount();

   // Create a new symbol in the same bucket, which will notice the dead entry and trigger cleanup.
   // Note: relies on SymbolTable's use of String::hashCode which collides for these two values.
   Symbol* entry2 = SymbolTable::new_symbol("hash_collision_397476851");

   ASSERT_EQ(entry2->refcount(), 1) << "Symbol refcount just created is 1";
 }
```

This test fails, entry2's refcount is actually 2 at this point because of the cleanup logic incrementing the refcount via equals. I have a patch to fix this.

Note that I observed this behaviour in a real application which churns through a lot of short-lived LambdaForms classes. I have attached a reproducer (ClassChurn.java) for the more realistic class churn scenario, where this leak (and possibly some others) can be observed. You can observe the RSS of the process growing over time, and the symbol table size can be observed growing via NativeMemoryTracking or jcmd VM.symboltable. I run it with `java -Xms200M -Xmx200m -XX:MaxMetaspaceSize=200M -XX:+AlwaysPreTouch ClassChurn.java`.
Comments
21u fix request. Clean backport to fix small memory leak.
24-08-2023

17u fix request. Fairly simple backport to fix small memory leak.
22-08-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk21u/pull/73 Date: 2023-08-21 08:32:32 +0000
21-08-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk17u-dev/pull/1674 Date: 2023-08-18 16:31:51 +0000
18-08-2023

Changeset: 4b2703ad Author: Oli Gillespie <ogillespie@openjdk.org> Committer: Aleksey Shipilev <shade@openjdk.org> Date: 2023-08-14 15:58:03 +0000 URL: https://git.openjdk.org/jdk/commit/4b2703ad39f8160264eb30c797824cc93a6b56e2
14-08-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/15137 Date: 2023-08-03 11:21:27 +0000
03-08-2023

Seems to be introduced by original JDK-8195100 in JDK 12, so it should not affect 11u and 8u.
03-08-2023