Hi Steven,
I think you are onto something. Given this example
public class TypeParamsDemo {
public static void main(String[] args) throws Exception {
Method method = TypeParamsDemo.class.getMethod("getList",
Set.class);
TypeVariable<Method>[] tp = method.getTypeParameters();
}
public <T extends Number> List<T> getList(Set<T> set) {
return new ArrayList<>(set);
}
}
Then:
tp[0].bounds[0].path[0].name is "java.lang.Number" and is a newly
created String parsed from the type signature
"<T:Ljava/lang/Number;>(Ljava/util/Set<TT;>;)Ljava/util/List<TT;>;".
The result of method.getTypeParameters() is cached in
Method.genericInfo, so once initialized by a caller (e.g. by
Jackson/REST easy) it won't go away.
Imho interning the class name should be fine since it refers to an
existing class.
Best
Johannes
On 09/08/2025 01:10, Steven Schlansker wrote:
Hello core-libs-dev, happy Friday!
While diagnosing an out of memory situation in our application, I noticed a
surprising source of memory usage.
While this is not so severe to actually cause our OOM, it seems wasteful and I
thought to bring it to your attention.
We use reflection-based technologies like Jackson JSON and RESTEasy that
retrieve generic type information from many of our classes.
Our profiler provides diagnostics of wasted space due to duplicate objects,
particularly Strings.
The analysis highlights many thousands of copies of String instances holding the full
name of a class, e.g. "java.util.Optional"
or "com.mycompany.Id". The path to GC route looks like:
String <- sun.reflect.generics.tree.SimpleClassTypeSignature <- Object[] <- ArrayList <-
ClassTypeSignature <- MethodTypeSignature <- MethodRepository <- Method
Seeing how these SimpleClassTypeSignature instances are created, it looks like
they come from the sun.reflect.generics.parser.SignatureParser which calls
`input.substring(mark, index)`, possibly with a call to `replace('/', '.')` to
munge the package name. In all but the simplest of cases, this will return a
new String
for every call.
Since this String is representing a Class name, the cardinality should by its
nature be very low. For each unique type, there will be many methods referring
to it.
Additionally, this generic information is lazy-loaded at most once per Method
object.
Therefore, SimpleClassTypeSignature.n seems like a natural place to apply
String.intern(), for example changing:
public static SimpleClassTypeSignature make(String n,
boolean dollar,
TypeArgument[] tas){
return new SimpleClassTypeSignature(n, dollar, tas);
}
to intern the name:
public static SimpleClassTypeSignature make(String n,
boolean dollar,
TypeArgument[] tas){
return new SimpleClassTypeSignature(n.intern(), dollar, tas);
}
With any luck, maybe this can even share the same string instance as the class
itself uses.
Am I correct in thinking this would be a moderately nice improvement, for a
relatively cheap cost?
Or perhaps there's some reason this isn't a good idea?
Thank you for your thoughts on the subject,
Steven