Skip to content

TypeFactory cache prevents garbage collection of custom ClassLoader #489

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

Closed
sftwr-ngnr opened this issue Jun 13, 2014 · 4 comments
Closed

Comments

@sftwr-ngnr
Copy link

Hi,

I use JSON (de)serialization via an ObjectMapper. Our application has a plug-in infrastructure where each plugin will get its own ClassLoader. The problem now is that the cache in the com.fasterxml.jackson.databind.type.TypeFactory keeps references to classes of our plug-in which prevents the garbage collection of those ClassLoaders on a plug-in shutdown. Of course, its not the cache itself but the keys / values used
(ClassKey._class / JavaType._class).

Please find a strong simplified working example below:

Currently we use jackson-databind 2.2.2, but the issue also arises in 2.4.

A cool fix would be to use weak references but I guess this will have too much impact. Another simple solution would be to offer a TypeFactory.clearCache() method or something similar.

Thanks & regards,
Urs

package jacksontest;

import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.type.TypeParser;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;

public class Test {

    public static void main(String[] args) throws Exception {
        CustomClassLoader ccl = new CustomClassLoader(Test.class.getClassLoader());
        WeakReference<CustomClassLoader> wr = new WeakReference<Test.CustomClassLoader>(ccl);

        TypeFactory tf = TypeFactory.defaultInstance();
        TypeParser tp = new TypeParser(tf);

        ClassLoader currentCCL = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(ccl);
            //if you comment the following line, the classloader will be collected as expected. 
            tp.parse("jacksontest.Test$TestClass");
        } finally {
            Thread.currentThread().setContextClassLoader(currentCCL);
        }

        ccl = null;
        System.gc();

        System.out.println("CustomClassLoader gone? " + (wr.get() == null ? ":)" : ":("));
    }

    static class CustomClassLoader extends ClassLoader {

        public CustomClassLoader(ClassLoader parent) {
            super(parent);
        }

        private Class<?> getClass(String name) throws ClassNotFoundException {
            String file = name.replace('.', File.separatorChar) + ".class";
            byte[] b = null;
            try {
                b = loadClassData(file);
                Class<?> c = defineClass(name, b, 0, b.length);
                resolveClass(c);
                return c;
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (name.startsWith("jacksontest.")) {
                return getClass(name);
            }
            return super.loadClass(name);
        }

        private byte[] loadClassData(String name) throws IOException {
            InputStream stream = getClass().getClassLoader().getResourceAsStream(name);
            int size = stream.available();
            byte buff[] = new byte[size];
            DataInputStream in = new DataInputStream(stream);
            in.readFully(buff);
            in.close();
            return buff;
        }
    }

    static class TestClass {
    }
}
@cowtowncoder
Copy link
Member

Ok I will need to think about this one. I can see why this is problematic for your case.

At minimum, method to clear cache would certainly be doable.
But I don't rule out weak refs either; esp. if this could be done with a MapperFeature or such. But before that I need to see how code worked again.

@cowtowncoder cowtowncoder changed the title TpyeFactory cache prevents garbage collection of custom ClassLoader TypeFactory cache prevents garbage collection of custom ClassLoader Jun 13, 2014
@cowtowncoder
Copy link
Member

As per check-in, added TypeFactory.clearCache() just to get something in 2.4.1; will think about weak refs afterwards.

@cowtowncoder
Copy link
Member

I think that the use of weak/soft references is problematic (for performance, code complexity), so addition of clearCache() should suffice.

But one thing that could be done, if this is still an issue, would be to consider possibility of plugging in different _typeCache instance, which could use soft/weak ref wrapping either by sub-classing or (perhaps better) by adding withTypeCache() method.
I'll leave the issue open as I do not have immediate plans or time to do this, but if that seems useful it could be added in future.

@cowtowncoder
Copy link
Member

Closing for now; may be re-filed if there are good improvement ideas.

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

No branches or pull requests

2 participants