byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

bytebuddy is throwing java.lang.OutOfMemoryError: Metaspace when proxy classes are created

Open vinayakbmalagatti opened this issue 1 year ago • 2 comments

We are creating the proxy class using the adapter factory like below code snippet

private static final AdapterMethodInterceptor ADAPTER_METHOD_INTERCEPTOR = new AdapterMethodInterceptor();

T adapter = new ByteBuddy()
			        .subclass(clazz)
			        .method((ElementMatchers.not(ElementMatchers.isNative())))
			        .intercept(MethodDelegation.to(ADAPTER_METHOD_INTERCEPTOR))
			        .make()	      .load(AdapterFactory.class.getClassLoader(),ClassLoadingStrategy.Default.INJECTION.with(AdapterFactory.class.getProtectionDomain())
			        .getLoaded().newInstance();
			        adapter.setAdaptee(adaptee);

public static class AdapterMethodInterceptor {

        @RuntimeType
        public Object intercept(@This Adapter<?> adapter,@Origin Method adapterMethod,@AllArguments Object[] args,@SuperMethod(nullIfImpossible = true) Method superMethod) throws Throwable {
           
            if (!Modifier.isAbstract(adapterMethod.getModifiers()) && !Modifier.isNative(adapterMethod.getModifiers())) {
            	 return superMethod.invoke(adapter, args);
            } else if (adapterMethod.getName().equals("adapt")) {
                 AdapteeInvoker adapteeInvoker = (AdapteeInvoker) args[0];
                 Object adaptee = args[1];
                 Method adapteeMethod = (Method) args[2];
                 Object[] invocationArgs = (Object[]) args[3];

                return adapteeInvoker.invoke(adaptee, adapteeMethod, invocationArgs);
            } else if (adapterMethod.getName().equals("handleNullAdaptee")) {
                  throw new IllegalStateException(MessageBundle.localizeMessage(MsgKey.UTIL_NULL_ADAPTEE, adapter.getClass().getName()));
            }

            if (adapter.getAdaptee() == null) {
                adapter.handleNullAdaptee();
            }
            Class<?> adapteeClass = adapter.getAdaptee().getClass();

            Method adapteeMethod = adapteeClass.getMethod(adapterMethod.getName(), adapterMethod.getParameterTypes());
         
            adapteeMethod.setAccessible(true);
         
            if (!adapterMethod.getReturnType().isAssignableFrom(adapteeMethod.getReturnType())) {
                throw new NoSuchMethodException(adapterMethod.toString());
            }

           return adapter.adapt(new AdapteeInvokerImpl(), adapter.getAdaptee(), adapteeMethod, args);
        }

    }
    
    public static class AdapteeInvokerImpl implements AdapteeInvoker {
        @Override
        public Object invoke(Object adaptee, Method adapteeMethod, Object[] args) throws Throwable {
            try {
                return adapteeMethod.invoke(adaptee, args);
            } catch (Exception e) {
                throw e.getCause();
            }
        }
    }

When we try to run the program to create the proxy classes, we are getting below error and all the java thread are running infinitely. In the program it calls multiple times to create the proxy classes.

Caused by: java.lang.IllegalStateException: java.lang.OutOfMemoryError: Metaspace at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Direct.defineClass(ClassInjector.java:689) at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection.injectRaw(ClassInjector.java:284) at net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase.inject(ClassInjector.java:118) at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$InjectionDispatcher.load(ClassLoadingStrategy.java:241) at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:101) at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6317) at com.org.common.util.AdapterFactory.buildAdapter(AdapterFactory.java:118) at com.org.oracle.core.jdbc.OracleConnectionFactory.createOCIJDBCConnectionInNewJvm(OracleConnectionFactory.java:141) ... 59 common frames omitted Caused by: java.lang.OutOfMemoryError: Metaspace

Environment details: Java: tried with 2 versions and java options are "-Xms256m -Xmx2G -XX:MaxMetaspaceSize=256m -XX:ParallelGCThreads=8" openjdk version "1.8.0_242" OpenJDK Runtime Environment (build 1.8.0_242-b08) OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)

openjdk version "11.0.19" 2023-04-18 LTS OpenJDK Runtime Environment (Red_Hat-11.0.19.0.7-1.el7_9) (build 11.0.19+7-LTS) OpenJDK 64-Bit Server VM (Red_Hat-11.0.19.0.7-1.el7_9) (build 11.0.19+7-LTS, mixed mode, sharing)

OS: RHEL 7.8

ByteBuddy version: 1.14.1

Thanks, Vinayak

vinayakbmalagatti avatar Jun 09 '23 08:06 vinayakbmalagatti

@raphw

Is there a way to implement caching for the below solution? Can you provide us a way to cache the classes so that the metaspace doesn't get filled up?

T adapter = new ByteBuddy() .subclass(clazz) .method((ElementMatchers.not(ElementMatchers.isNative()))) .intercept(MethodDelegation.to(ADAPTER_METHOD_INTERCEPTOR)) .make().load(AdapterFactory.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION.with(AdapterFactory.class.getProtectionDomain()) .getLoaded().newInstance(); adapter.setAdaptee(adaptee);

guru1306 avatar Jun 15 '23 14:06 guru1306

You should use a TypeCache and not create new classes for each proxy.

raphw avatar Jun 19 '23 02:06 raphw