Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Classloader not garbage collected due to reference from jackson ObjectMapper #764

Open
jin-harmoney opened this issue Feb 6, 2025 · 4 comments

Comments

@jin-harmoney
Copy link

Describe the bug

We're building an application on Quarkus and see increasing metaspace memory usage on every live reload. This is caused by the application classloader that is not garbage collected on live-reload.

This seems to be caused by the static ObjectMapper in ObjectMapperWrapper. The ObjectMapper is not loaded by the application classloader, but it does contain references to classes of our application (custom deserializers etc). Hence, it blocks the garbage collection of the application classloader.

To Reproduce

Let me know if I need to provide a reproducer (minimal Quarkus project). Since it's a live-reload issue, I don't think it's possible to add a test for this scenario.

Expected behavior

An elegant solution would be to able to clear all caches used by the ObjectMapper. I raised that question here: FasterXML/jackson-databind#4953.

An alternative solution would be to be able to set the ObjectMapper INSTANCE to null, so it can be garbage collected.

Similar to hibernate-envers, a disintegrate method could be introduced that:

  • Removes any references to ObjectMappers
  • Calls objectMapper.clear() when such a method would be made available in jackson.

Additional context

I described a similar case for hibernate-envers quarkusio/quarkus#46102.

I'm happy to contribute with a PR!

@jin-harmoney
Copy link
Author

jin-harmoney commented Feb 6, 2025

PR raised on jackson-databind: FasterXML/jackson-databind#4954 to add support for objectMapper.clearCaches().

If the PR would be accepted and since the static objectMapper INSTANCE can be accessed publicly, I guess there's no need for changes in this repo 🤔 Unless there's a way to call clearCaches() automatically as in hibernate-envers. That would avoid that users bump into classloader leaks.

@vladmihalcea
Copy link
Owner

You can investigate the issue, and if you find a good solution to fix it, then send me a Pull Request, and I'll review it.

@jin-harmoney
Copy link
Author

@vladmihalcea my PR to jackson-databind is merged, so I can resolve this from within our application by calling ObjectMapperWrapper.INSTANCE.getObjectMapper().clearCaches().

To try to avoid the same issue for other users of hypersistence-utils, I investigated if there's a possibility to automatically clean up the objectMapper instance.

Envers works with the Integrator interface, which provides a disintegrate method to do cleanup.

Hypersistence utils works with the TypeContributor interface, which does not provide a similar method. So at this moment, I don't think it's possible to do something similar as in Envers.

Would it make sense to request an extension of the TypeContributor interface?

Another possible option would be to avoid that jackson modules from the application using hypersistence-utils are loaded in the static objectMapper in ObjectMapperWrapper. Classloaders are not my area of expertise, but I'll investigate if this would be a viable option. This might be a cleaner solution than what I suggest above, because it avoids the classleak alltogether instead of fixing it on cleanup.

@vladmihalcea
Copy link
Owner

Envers works with the Integrator interface, which provides a disintegrate method to do cleanup.

You can also add an Integrator like the one provided by Envers and clear the caches in dissintegrate. You will have to create the Integrator and the Service Provider file that defines it so that Hibernate can load it via the Java Service Provider API.

Or, you can change the ObjectMapperWrapper to use a WeakReference.

Instead of:

public static final ObjectMapperWrapper INSTANCE = new ObjectMapperWrapper();

You change it to:

private static final WeakReference<ObjectMapperWrapper> INSTANCE = new WeakReference<>(new ObjectMapperWrapper());

public static ObjectMapperWrapper getInstance() {
    return INSTANCE.get();
}

Try it and see if it works, and if it does, then we can change it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants