> _(NB: For this leak to occur, an application needs to be either creating and 
> discarding linkers frequently, or creating and discarding class loaders 
> frequently while creating Dynalink type converters for classes in them. 
> Neither is typical usage, although they can occur in dynamic code loading 
> environments such as OSGi.)_
> 
> I'll explain this one in a bit more detail. Dynalink creates and caches 
> method handles for language-specific conversions between types. Each 
> conversion is thus characterized by a `Class` object representing the type 
> converted from, and a `Class` object representing the type converted to, thus 
> they're keyed by a pair of `(Class, Class)`. Java API provides the excellent 
> `ClassValue` class for associating values with a single class, but it lacks 
> the – admittedly more niche – facility for associating a value with a pair of 
> classes. I originally solved this problem using something that looks like a 
> `ClassValue<Map<Class<?>, T>>`. I say "looks like" because Dynalink has a 
> specialized `ClassMap` class that works like `Map<Class<?>, T>` but it also 
> goes to some pains to _not_ establish strong references from a class loader 
> to either its children or to unrelated class loaders, for obvious reasons.
> 
> Surprisingly, the problem didn't lie in there, though. The problem was in the 
> fact that `TypeConverterFactory` used `ClassValue` objects that were its 
> non-static anonymous inner classes, and further the values they computed were 
> also of non-static anonymous inner classes. Due to the way `ClassValue` 
> internals work, this led to the `TypeConverterFactory` objects becoming 
> anchored in a GC root of `Class.classValueMap` through the synthetic `this$0` 
> reference chains in said inner classes… talk about non-obvious memory leaks. 
> (I guess there's a lesson in there about not using `ClassValue` as an 
> instance field, but there's a genuine need for them here, so…)
> 
> … so the first thing I did was I deconstructed all those anonymous inner 
> classes into named static inner classes, and ended up with something that 
> _did_ fix the memory leak (yay!) but at the same time there was a bunch of 
> copying of constructor parameters from outer classes into the inner classes, 
> the whole thing was very clunky with scary amounts of boilerplate. I felt 
> there must exist a better solution.
> 
> And it exists! I ended up replacing the `ClassValue<ClassMap<T>>` construct 
> with a ground-up implementation of `BiClassValue<T>`, representation of 
> lazily computed values associated with a pair of types. This was the right 
> abstraction the `TypeConverterFactory` code needed all along. 
> `BiClassValue<T>` is also not implemented as an abstract class but rather it 
> is a final class and takes a `BiFunction<Class<?>, Class<?>, T>` for 
> computation of its values, thus there's never a risk of making it an 
> instance-specific inner class (well, you still need to be careful with the 
> bifunction lambda…) It also has an even better solution for avoiding strong 
> references to child class loaders.
> 
> And that's why I'm not submitting here the smallest possible point fix to the 
> memory leak, but rather a slightly larger one that architecturally fixes it 
> the right way.
> 
> There are two test classes, they test subtly different scenarios. 
> `TypeConverterFactoryMemoryLeakTest` tests that when a `TypeConverterFactory` 
> instance becomes unreachable, all method handles it created also become 
> eligible for collection. `TypeConverterFactoryRetentionTests` on the other 
> hand test that the factory itself won't prevent class loaders from being 
> collected by virtue of creating converters for them.

Attila Szegedi has updated the pull request incrementally with one additional 
commit since the last revision:

  Avoid null values in fields again

-------------

Changes:
  - all: https://git.openjdk.java.net/jdk/pull/1918/files
  - new: https://git.openjdk.java.net/jdk/pull/1918/files/a8d81ebb..cb399d7b

Webrevs:
 - full: https://webrevs.openjdk.java.net/?repo=jdk&pr=1918&range=05
 - incr: https://webrevs.openjdk.java.net/?repo=jdk&pr=1918&range=04-05

  Stats: 18 lines in 1 file changed: 0 ins; 9 del; 9 mod
  Patch: https://git.openjdk.java.net/jdk/pull/1918.diff
  Fetch: git fetch https://git.openjdk.java.net/jdk pull/1918/head:pull/1918

PR: https://git.openjdk.java.net/jdk/pull/1918

Reply via email to