Announcement Announcement Module
Collapse
No announcement yet.
OutOfMemoryErrors - Possibly caused by proxy generation? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • OutOfMemoryErrors - Possibly caused by proxy generation?

    Hi all.

    I am working on something relating to performance and memory problems relating to Spring AOP.

    My concern has arisen as our system scales up and more and more business objects are proxied as they are loaded out of the persistent store. At a high level it seems when we say


    Code:
    BusinessModel target ... // loaded model from store to be proxied
    ProxyFactory aop = new ProxyFactory(target);
    aop.setProxyTargetClass(true);
    
    BusinessModel proxy = (BusinessModel)aop.getProxy();
    Now no matter what I try, including various tweakings of the above, CGLIB seems to generate a new proxy class each time this happens (event if the business model is of the same type. For example, if the BusinessModel target is of type SalesOrder and we load all 20 of them from the store and wrap them in proxies as above, we end up with 20 different classes being generated! This means over time the class loader will fill up with classes as more and more models are loaded and reloaded and we run out of memory.

    I've also tried caching proxy factory instances by the business model classname and then setting the target on the cached factory before creating the proxy, but this caused unpredictable things to happen.

    Note that we are not using spring beans for our BusinessModels (we took spring on board after it was too late) - but i assume that someone has got proxying working like this using springbeans for their business models...

    Ideally I'd like to reuse the same proxy class for each type of BusinessModel - I assumed that is how it would work initially.

    Anyone have any ideas on what going wrong?

    Regards,

    Bart

  • #2
    Code:
    BusinessModel target ... // loaded model from store to be proxied
    ProxyFactory aop = new ProxyFactory(target);
    aop.setProxyTargetClass(true);
    You should not create Factories for every request - create a factory with one
    configuration and keep it or use the static method. I haven't used proxies programatically but normally the proxies should be recycled (CGLIB has support for that, AFAIK).
    Try to do some profiling and review the lifecycle of the objects inside your workflow - if you have stateless objects (which are the way to go for scalability) then use singleton instances.

    Comment


    • #3
      Bart,

      What version of Spring are you using. In versions prior to 1.1.3 the caching wasn't functioning correctly, however, in recent versions all Spring internal classes are correctly implemented for the contract needed by caching. You should try to ensure that your advice classes also implement equals() and hashCode() correctly. Also we can only reuse a class when the advice chain is exactly the same - that is equals() returns true for all advice in the chain.

      Rob

      Comment


      • #4
        Rob,

        It appears we are using version 1.0.2 (gasp). I had a play with the latest version last night and it seemed to do the right thing in some simple unit tests.

        Thanks for the propmpt response.

        Bart

        Comment


        • #5
          Hi bjs, if you're programmatically building proxies, my guess is you're building a lot of them. I've found building Java dynamic proxies to be a lot more performant (to create) then using CGLIB. If you can, you may want to reconsider the setProxyTargetClass(true) call.

          Comment


          • #6
            As Rob points out, this should work OK in recent Spring versions. However, I agree with Steven: I would try to use JDK proxies, which are very lightweight, if possible in this case.

            Comment


            • #7
              i know this thread is a bit old, but i recently ran into a similar problem that i was able to fix, so i'm going to add some more detail here in case anyone comes across this in the future.

              if you are using ProxyFactory programmatically to proxy a class, CGLIB will be used to generate a new class for the proxying.

              CGLIB keeps a cache of these generated classes, so it won't always generate a new class. it uses a Map for the cache, and it uses a ProxyCallbackFilter object along with some other objects as the key for that Map.

              If you're seeing lots of new classes being generated (you can check by using -XX:+TraceClassLoading), and consequently OutOfMemoryErrors in your permanent generation, a likely cause is CGLIB creating new classes because you are not taking advantage of the cache.

              as robh says in his post, make sure that you are correctly implementing equals and hashCode. Also make sure that your advisors are the same. Some Advisors might not implement equals or hashCode, so you have to make sure you're using the same instance.

              take a look at the equals method of org.springframework.aop.framework.Cglib2AopProxy$P roxyCallbackFilter

              thats one of the key objects that is part of the key of the CGLIB cache. if you can get the equal method of that object to give you the correct results, you should be ok.

              hope this helps.

              --alex

              Comment


              • #8
                Thanks Alex, that's a good summary.

                And in general, if you need to create lots of proxies, try to code against interfaces so you can use JDK proxies.

                Comment


                • #9
                  CGLIB cache will not be used when using introduction advisors

                  When using introduction advisors, the equals method of org.springframework.aop.framework.Cglib2AopProxy$P roxyCallbackFilter will always return false, because it calls the following method:
                  Code:
                  private boolean equalsPointcuts(Advisor a, Advisor b) {
                    return (a instanceof PointcutAdvisor && b instanceof PointcutAdvisor 
                             && ObjectUtils.nullSafeEquals(((PointcutAdvisor) a).getPointcut(),  ((PointcutAdvisor) b).getPointcut()));
                  }
                  This method may only return true if the advisors inherit from PointcutAdvisor. As mentioned by Alex in this thread, Cglib2AopProxy$ProxyCallbackFilter.equals returning false will prevent CGLIB from using its class cache.

                  As an example, the following code will eventually cause an "java.lang.OutOfMemoryError: PermGen space" error:

                  Code:
                  public void testOutOfPermGenMemoryDuringProxyCreation()
                    {
                      Target target = new Target();
                      
                      for (int i = 0; i < 10000; i++)
                      {
                        ProxyFactory pf = new ProxyFactory();
                        pf.setTarget(target);
                        //forces CGLIB use
                        pf.setProxyTargetClass(true);
                        Advice advice = new DelegatingIntroductionInterceptor(new SomeInterfaceImpl());
                        Advisor advisor = new DefaultIntroductionAdvisor(advice);
                        pf.addAdvisor(advisor);
                        SomeInterface proxy = (SomeInterface) pf.getProxy();
                        proxy.doSomething();  
                      }
                     
                    }
                    
                    public static class Target
                    {
                    }
                    
                    private static interface SomeInterface
                    {
                      public void doSomething();
                    }
                    
                    private static class SomeInterfaceImpl implements SomeInterface 
                    {
                      public void doSomething()
                      {
                        System.out.println("something");
                      }
                    }
                  The method introduceInterfaces in class DynamicBeanIntroductor (from Spring Modules) has a code very similar to this test and will probably cause the same error, if executed many times:
                  Code:
                  public class DynamicBeanIntroductor extends AbstractDynamicIntroductor {    
                  
                   public Object introduceInterfaces(Object target, Class[] introducedInterfaces) {
                          ProxyFactory proxyFactory = new ProxyFactory();        
                          proxyFactory.setProxyTargetClass(true);
                          proxyFactory.addAdvisor(new BeanIntroductorAdvisor(introducedInterfaces));
                          proxyFactory.setTarget(target);        
                          return proxyFactory.getProxy();
                      }
                  Valdo Noronha

                  Comment

                  Working...
                  X