JDK-8229011 : Leak (.invoke.LambdaForm, invoke.BoundMethodHandle)
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Affected Version: 9,11,12,14
  • Priority: P4
  • Status: Resolved
  • Resolution: Duplicate
  • OS: generic
  • CPU: x86_64
  • Submitted: 2019-07-27
  • Updated: 2024-10-04
  • Resolved: 2024-10-04
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.
Other
tbdResolved
Related Reports
Duplicate :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Tested on the latest OSX with Ubuntu Linux using 
the latest builds from https://adoptopenjdk.net/upstream.html
8u222-b10 - no problem
11.0.4+11 - leak
jdk-12.0.2+10 - leak

A DESCRIPTION OF THE PROBLEM :
Using jdk 11 and 12 we experience a memory leak using nashorn (we know that is deprecated)

from the heap dump we can see the number of java.lang.invoke.LambdaForm and java.lang.invoke.BoundMethodHandle$Species_LL  going up each iteration.

this does not happen by switching to jdk 8

REGRESSION : Last worked in version 8u221

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
create an instance of the nashorn script engine and invoke a java injected class method in a loop.
with a jdk 11 or 12 you can see that the memory keeps going up and from the heap dump you can see a lot of LambdaForm and BoundMethodHandle objects. while with jdk 8 this does not happen.

ACTUAL -
from the heap dump you can see the number of java.lang.invoke.LambdaForm and java.lang.invoke.BoundMethodHandle$Species_LL  going up each iteration.

  Class Name                                                                    | Objects | Shallow Heap
  -------------------------------------------------------------------------------------------------------
  java.lang.invoke.LambdaForm$Name                                              |   4,857 |      155,424
  |- java.lang.Object[]                                                         |   4,375 |      124,784
  |- java.lang.invoke.LambdaForm$Name[]                                         |     862 |       49,000
  |  |- java.lang.invoke.LambdaForm                                             |     857 |       41,136
  |  |  |- java.lang.invoke.BoundMethodHandle$Species_LL                        |     938 |       37,520
  |  |  |  |- java.lang.invoke.BoundMethodHandle$Species_LL                     |     386 |       15,440
  |  |  |  |  |- java.lang.invoke.DirectMethodHandle                            |     314 |       10,048
  |  |  |  |  |- jdk.nashorn.internal.runtime.AccessorProperty                  |     165 |        9,240
  |  |  |  |  |- java.lang.invoke.LambdaForm                                    |      40 |        1,920
  |  |  |  |  |- java.lang.Object[]                                             |      41 |        1,880
  |  |  |  |  |- java.lang.invoke.MethodHandle[]                                |      28 |          896
  |  |  |  |  |- java.lang.invoke.BoundMethodHandle$Species_LLIL                |      12 |          576
  |  |  |  |  |- java.lang.invoke.DirectMethodHandle$Accessor                   |      14 |          560
  |  |  |  |  |- jdk.nashorn.internal.runtime.Context$BuiltinSwitchPoint        |      22 |          528
  |  |  |  |  |- java.lang.invoke.BoundMethodHandle$Species_LLI                 |      12 |          480
  |  |  |  |  |- java.lang.invoke.BoundMethodHandle$Species_LLLL                |      10 |          480
  |  |  |  |  |- java.lang.invoke.SwitchPoint                                   |      10 |          240
  |  |  |  |  |- java.lang.invoke.BoundMethodHandle$Species_LL                  |       4 |          160
  |  |  |  |  |- jdk.nashorn.internal.runtime.linker.InvokeByName               |       4 |           96
  |  |  |  |  |- java.util.concurrent.ConcurrentHashMap$Node                    |       2 |           64
  |  |  |  |  |- java.lang.Class                                                |       1 |           40
  |  |  |  |  |- jdk.nashorn.internal.runtime.ScriptFunctionData$GenericInvokers|       1 |           24
  |  |  |  |  '- Total: 16 entries                                              |         |             
  |  |  |  |- java.lang.invoke.DirectMethodHandle                               |     322 |       10,304
  |  |  |  |- jdk.nashorn.internal.runtime.AccessorProperty                     |     168 |        9,408
  |  |  |  |- java.lang.invoke.BoundMethodHandle$Species_LLL                    |      57 |        2,280
  -------------------------------------------------------------------------------------------------------


---------- BEGIN SOURCE ----------
package test;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class TestJsMemLeak {
  private static final String JS_TEST_NAME = "test";
  private static final String JS_TEST_SOURCE =
      "for (var i = 0; i < 10; ++i) {\n" +
      "    JsJavaUtil.testFunc();\n" +
      "}";

  public static final class JsJavaUtil {
    private long counter = 0;

    public long testFunc() {
      return counter++;
    }
  }

  public static void main(final String[] args) throws Exception {
    System.setProperty("nashorn.args", "--no-java -doe --language=es5");

    for (long i = 0; true; ++i) {
      final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
      scriptEngine.put("JsJavaUtil", new JsJavaUtil());
      scriptEngine.put("script", JS_TEST_SOURCE);
      scriptEngine.put("scriptName", JS_TEST_NAME);
      scriptEngine.eval("load({ script: script, name: scriptName })");
      System.gc();
      if (i % 100 == 0) {
        System.out.println(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
      }
    }
  }
}

---------- END SOURCE ----------

FREQUENCY : always



Comments
This is likely a duplicate of JDK-8198540, will try to confirm whether the problem persists on Java 16 or later when JDK-8198540 was fixed.
04-10-2024

Nashorn has bee removed in JDK 15 (https://openjdk.java.net/jeps/372). Lower the priority to P4. It'd help if the nashorn team can produce a simple reproducer without using dynalink.
11-06-2020

According to hg bisect: The first bad revision is: changeset: 1502:e08b29ee795e user: attila date: Mon Nov 09 14:04:43 2015 +0100 summary: 8141538: Make DynamicLinker specific to a Context in Nashorn Kindly asking the nashorn team to investigate this further.
26-11-2019

Potentially an issue with nashorn changes between 9b92 and 9b93; the 9b92 nashorn library run with 9b93 hotspot is fine: [jdk9-cpu]$ for i in `find . -name .hg -type d`; do pushd $i; hg update -C -r jdk9-b93 ; popd ; done [...] [jdk9-cpu]$ bash ./configure --disable-warnings-as-errors --with-devkit=`pwd`/../jdk9-devkit --with-plugin-devkit=`pwd`/../jdk9-plugin-devkit Running custom generated-configure.sh configure: Configuration created at Tue Nov 26 12:58:18 CET 2019. [...] checking if build directory is on local disk... [jdk9-cpu]$ make clean images Compiling 5 files for BUILD_GENMODULESLIST Building targets 'clean images' in configuration 'linux-x86_64-normal-server-release' [...] Finished building targets 'clean images' in configuration 'linux-x86_64-normal-server-release' [jdk9-cpu]$ build/linux-x86_64-normal-server-release/images/jdk/bin/java -Xmx20m TestJsMemLeak 2453248 2415408 2443224 2457320 2472280 2485344 2494808 2511120 2522512 2532408 2542424 2551936 [...] 3986496 3995456 4004840 4013800 4022760 4031720 4040680 [jdk9-cpu]$ cd nashorn/ [nashorn]$ hg update -C -r jdk9-b92 63 files updated, 0 files merged, 25 files removed, 0 files unresolved [nashorn]$ cd .. [jdk9-cpu]$ make clean images Building targets 'clean images' in configuration 'linux-x86_64-normal-server-release' [...] Finished building targets 'clean images' in configuration 'linux-x86_64-normal-server-release' [jdk9-cpu]$ build/linux-x86_64-normal-server-release/images/jdk/bin/java -Xmx20m TestJsMemLeak 2447072 2399136 2417560 2422136 2428064 [...] 2340256 2340256 2340544 2340952 2341016 2341016 2341016
26-11-2019

Independent of collector apparently (tested both Parallel and G1).
25-11-2019

Introduced in 9b93.
22-11-2019

Class histogram some time before running out of memory with -Xmx20M: WARNING: Ran out of C-heap; undercounted 26 total instances in data below num #instances #bytes class name (module) ------------------------------------------------------- 1: 64922 2077504 java.lang.ClassValue$Entry (java.base@13-ea) 2: 16269 1432720 [Ljava.util.WeakHashMap$Entry; (java.base@13-ea) 3: 32534 1301360 java.util.WeakHashMap$Entry (java.base@13-ea) 4: 48655 1167720 java.lang.ClassValue$Version (java.base@13-ea) 5: 16344 1046016 java.util.concurrent.ConcurrentHashMap (java.base@13-ea) 6: 13910 803392 [B (java.base@13-ea) 7: 16253 780144 java.util.WeakHashMap (java.base@13-ea) 8: 48655 778480 java.lang.ClassValue$Identity (java.base@13-ea) 9: 16782 671280 java.lang.ref.SoftReference (java.base@13-ea) 10: 16215 648600 jdk.dynalink.TypeConverterFactory (jdk.dynalink@13-ea) 11: 16283 521056 java.lang.ref.ReferenceQueue (java.base@13-ea) 12: 16215 518880 [Ljdk.dynalink.linker.GuardingTypeConverterFactory; (jdk.dynalink@13-ea) 13: 16215 518880 jdk.dynalink.TypeConverterFactory$1 (jdk.dynalink@13-ea) 14: 16215 518880 jdk.dynalink.TypeConverterFactory$2 (jdk.dynalink@13-ea) 15: 16215 518880 jdk.dynalink.TypeConverterFactory$3 (jdk.dynalink@13-ea) 16: 16214 518848 jdk.dynalink.TypeConverterFactory$1$1 (jdk.dynalink@13-ea) 17: 16215 389160 [Ljdk.dynalink.linker.ConversionComparator; (jdk.dynalink@13-ea) 18: 2706 327440 java.lang.Class (java.base@13-ea) 19: 13413 321912 java.lang.String (java.base@13-ea) 20: 16285 260560 java.lang.ref.ReferenceQueue$Lock (java.base@13-ea) 21: 3182 170816 [Ljava.lang.Object; (java.base@13-ea) 22: 16 133128 [Ljava.lang.ClassValue$Entry; (java.base@13-ea) 23: 19 132440 [C (java.base@13-ea) From heap dumps it looks like j.l.ClassValue$ClassValueMap holds everything alive; it is a WeakHashMap<ClassValue.Identity, java.lang.ClassValue$Entry>.
22-11-2019

[~hannesw] Could you clearify the roots and object graph you wrote in the 16:21 comment?
14-08-2019

Changed component to hotspot based on above findings.
02-08-2019

I've looked at heap dumps of this, and most of the java.lang.invoke.BoundMethodHandle$Species_LL instances have GC root references that vary slightly but mostly follow this pattern, where the middle link is the class representing some Nashorn class: static PLATFORM_LOADERinjava.lang.Class#3243 [GC root - sticky class] : ClassLoaders -> .... static <resolved_references>injava.lang.Class#1053 : NativeNumber$Prototype -> ... asTypeCacheinjava.lang.invoke.DirectMethodHandle#40 Nashorn classes themselves do not leak, and we create these method handles using standard java.lang.invoke functionality (no caching on Nashorn side).
02-08-2019

Hand off to VM team if we are not at fault.
02-08-2019

This issue is reproducible in jdk14 ea b04 also, This gives expected result in JDK8 and it is regression started from JDK9
02-08-2019