7 Replies Latest reply: Nov 16, 2012 5:44 AM by 974220 RSS

    Oracle ADF viewScope causing session size bloat

    974220
      As I'm sure you know, ADF introduces some additional scopes (pageFlowScope, viewScope and backingBeanScope) on top of the standard JSF ones. Our use of one of the ADF scopes, viewScope, appears to be causing our session size to bloat over time.

      Objects that are view scoped (e.g. our Backing Beans) are managed by ADF and appear to be put into the session in a org.apache.myfaces.trinidadinternal.application.StateManagerImpl$PageState object. The number of these objects in the session is equal to the org.apache.myfaces.trinidad.CLIENT_STATE_MAX_TOKENS in our web.xml configuration file.

      Once all of the tokens are ‘used up’, by navigating around the application, the oldest one of these objects is removed from the session and (should be) garbage collected at some point. However, the reclaim of this space is observed much later, after the session has expired. Because of this, when load testing the application we see the heap space usage gradually increasing, before causing the JVM to crash.

      The monitoring of the creation and destruction of our objects is done by adding log statements in the default constructor and in the finalize method (Which overrides the finalize method on object). The logging statements on object creation are seen when we would expect them, but the logging statements from the finalize method are only seen after session expiry. When a garbage collection is triggered using Oracle JRocket Mission Control we see the heap usage drop significantly, but don’t observe any logging from the finalize method calls.

      Does anyone have any thoughts on why the garbage collector might not be able to reclaim view scoped objects after they are removed from the session?

      Thanks in advance.

      P.S. I have already found VIEW SCOPE IS NOT RELEASING PROPERLY IN ADF which is a very closesly related thread, but unfortunately was not able to use the replies on there to resolve our issue. I've also posted this same question on Stack Overflow (http://stackoverflow.com/questions/13380151/lifetime-of-view-scoped-objects-oracle-adf). I'll try and update both threads if I find a solution.

      Edited by: 971217 on 14-Nov-2012 07:08
        • 1. Re: Oracle ADF viewScope causing session size bloat
          Frank Nimphius-Oracle
          Hi´,

          Objects that are view scoped (e.g. our Backing Beans) are managed by ADF and appear to be put into the session in a org.apache.myfaces.trinidadinternal.application.StateManagerImpl$PageState object. The number of these objects in the session is equal to the org.apache.myfaces.trinidad.CLIENT_STATE_MAX_TOKENS in our web.xml configuration file.

          The state token doesn't take care of view scope beans but view states that you may want to revisit using the back button. The view scope, as you correctly observe, is saved in a HashMap in session. However, as soon as you move away from a view for which you created a view scope bean, then bean is released for garbage collection. The easiest way to tell this is happening is to bind the value of a text field to a property in the view scope bean, navigate out of the view (to a second view) then back. The value shown in the text field should be reset to the default value in the view scope bean and no longer be the value you set.

          If this doesn't work for you, let us know or file a bug with a test case.

          Frank
          • 2. Re: Oracle ADF viewScope causing session size bloat
            974220
            Hi Frank,

            Thanks for your very useful reply. I've managed to recreate the problem today by doing the following.

            1. Create pageOne.jspx and pageTwo.jspx
            2. Create PageOneBB.java and PageTwoBB.java
            3. Register PageOneBB.java and PageTwoBB.java in the adfc-config.xml as view scoped managed beans.

            Then after building and deploying out to my Weblogic server I continue by doing the following:

            4. Open pageOne.jspx in a browser. Observe the constructor of pageOneBB being called and the correct default text being shown in the box. [Optional] Set the text value to a new string and click on the button.
            5. Get redirected to pageTwo.jspx. Observe the constructor of pageTwoBB being called and the correct default text being shown in the box. [Optional] Set the value to a new string and click on the button.
            6. Monitor the Weblogic server using Oracle JRocket Mission Control. Observe the large lists of booleans being created as expected (5,000,000 per click!).
            7. Note that this number is never reduced - even though the old view scoped beans should have been released for garbage collection.
            8. Repeat steps 4 and 5 until I see the Weblogic server crash due to a java.lang.OutOfMemoryError.
            9. Wait for all of the sessions to expire. I've set my session expiry to be 180s for the purpose of this test.
            10. After 180s observe the finalize method being called on all of the backing bean objects and the heap usage drop significantly.
            11. The server works again but the problem has been demonstrated in a reproducible way.

            adfc-config.xml
            <managed-bean>
                <managed-bean-name>pageOneBB</managed-bean-name>
                <managed-bean-class>presentation.adf.test.PageOneBB</managed-bean-class>
                <managed-bean-scope>view</managed-bean-scope>
            </managed-bean>
            
            <managed-bean>
                <managed-bean-name>pageTwoBB</managed-bean-name>
                <managed-bean-class>presentation.adf.test.PageTwoBB</managed-bean-class>
                <managed-bean-scope>view</managed-bean-scope>
            </managed-bean>
            pageOne.jspx
            <?xml version='1.0' encoding='utf-8?>
            <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
                xlmns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
                xmlns:c="http://java.sun.com/jsp/jstl/core" >
                <jsf:directive.page contentType="text/html;charset=UTF-8" />
                <f:view>
                    <af:document id="t" title="Page One">
                        <af:form>
                            <af:inputText id="pgOneIn" value="#{viewScope.pageOneBB.testData}" />
            
                            <af:commandButton id="pgOneButton" partialSubmit="true"
                                blocking="true" action="#{viewScope.pageOneBB.goToPageTwo}"
                                text="Submit" />
                        </af:form>
                    <af:document>
                </f:view>
            </jsp:root>
            pageTwo.jspx
            <?xml version='1.0' encoding='utf-8?>
            <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
                xlmns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
                xmlns:c="http://java.sun.com/jsp/jstl/core" >
                <jsf:directive.page contentType="text/html;charset=UTF-8" />
                <f:view>
                    <af:document id="t" title="Page Two">
                        <af:form>
                            <af:inputText id="pgTwoIn" value="#{viewScope.pageTwoBB.testData}" />
            
                            <af:commandButton id="pgTwoButton" partialSubmit="true"
                                blocking="true" action="#{viewScope.pageTwoBB.goToPageOne}"
                                text="Submit" />
                        </af:form>
                    <af:document>
                </f:view>
            </jsp:root>
            PageOneBB.java
            package presentation.adf.test;
            
            import java.io.IOException;
            import java.io.Serializable;
            import java.util.ArrayList;
            import java.util.List;
            
            import javax.faces.context.FacesContext;
            
            import org.apache.log4j.Logger;
            
            import logger.log4j.RuntimeConfigurableLogger;
            
            public class PageOneBB implements Serialiable
            {
            
                /** Default serial version UID. */
                private static final long serialVersionUID = 1L;
            
                /** Page one default text. */
                private String pageOneData = "Page one default text";
            
                /** A list of booleans that will become large. */
                private List<Boolean> largeBooleanList = new ArrayList<Boolean>();
            
                /** The logger */
                private static final Logger LOG = RuntimeConfigurableLogger.gotLogger(PageOneBB.class);
            
                /** Default constructor for PageOneBB. */
                public PageOneBB()
                {
            
                    for (int i = 0; i < 5000000; i++)
                    {
                        largeBooleanList.add(new Boolean(true));
                    }
            
                    if (LOG.isTraceEnabled())
                    {
                        LOG.trace("Constructor called on PageOneBB. This object has a hash code of " + this.hashCode());
                    }
                }
            
                /** Method for redirecting to page two. */
                public void goToPageTwo()
                {
                    try
                    {
                        FacesContext.getCurrentInstance().getExternalContext.redirect("pageTwo.jspx");
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            
               /**
                * {@inheritDoc}
                */
                @Override
                protected void finalize() throws Exception
                {
                    if (LOG.isTraceEnabled())
                    {
                        LOG.trace("Finalize method called on PageOneBB. This object has a hash code of " + this.hashCode());
                    }
                }
            
               /**
                * Set the testData
                *
                * @param testData
                *        The testData to set.
                */
                public void setTestData(String testData)
                {
                    if (LOG.isTraceEnabled())
                    {
                        LOG.trace("setTestData method called on PageOneBB with a parameter of " + testData);
                    }
                    this.pageOneData = testData;
                }
            
            
               /**
                * Get the testData
                *
                * @return The testData.
                */
                public String getTestData()
                {
                    if (LOG.isTraceEnabled())
                    {
                        LOG.trace("getTestData method called on PageOneBB");
                    }
                    return pageOneData;
                }
            
             }
            PageTwoBB.java
            package presentation.adf.test;
            
            import java.io.IOException;
            import java.io.Serializable;
            import java.util.ArrayList;
            import java.util.List;
            
            import javax.faces.context.FacesContext;
            
            import org.apache.log4j.Logger;
            
            import logger.log4j.RuntimeConfigurableLogger;
            
            public class PageTwoeBB implements Serialiable
            {
            
                /** Default serial version UID. */
                private static final long serialVersionUID = 1L;
            
                /** Page one default text. */
                private String pageTwoData = "Page two default text";
            
                /** A list of booleans that will become large. */
                private List<Boolean> largeBooleanList = new ArrayList<Boolean>();
            
                /** The logger */
                private static final Logger LOG = RuntimeConfigurableLogger.gotLogger(PageTwoBB.class);
            
                /** Default constructor for PageTwoBB. */
                public PageTwoBB()
                {
            
                    for (int i = 0; i < 5000000; i++)
                    {
                        largeBooleanList.add(new Boolean(true));
                    }
            
                    if (LOG.isTraceEnabled())
                    {
                        LOG.trace("Constructor called on PageTwoBB. This object has a hash code of " + this.hashCode());
                    }
                }
            
                /** Method for redirecting to page one. */
                public void goToPageOne()
                {
                    try
                    {
                        FacesContext.getCurrentInstance().getExternalContext.redirect("pageOne.jspx");
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            
                /**
                * {@inheritDoc}
                */
                @Override
                protected void finalize() throws Exception
                {
                    if (LOG.isTraceEnabled())
                    {
                        LOG.trace("Finalize method called on PageTwoBB. This object has a hash code of " + this.hashCode());
                    }
                }
            
               /**
                * Set the testData
                *
                * @param testData
                *        The testData to set.
                */
                public void setTestData(String testData)
                {
                    if (LOG.isTraceEnabled())
                    {
                        LOG.trace("setTestData method called on PageTwoBB with a parameter of " + testData);
                    }
                    this.pageTwoData = testData;
                }
            
            
               /**
                * Get the testData
                *
                * @return The testData.
                */
                public String getTestData()
                {
                    if (LOG.isTraceEnabled())
                    {
                        LOG.trace("getTestData method called on PageTwoBB");
                    }
                    return pageTwoData;
                }
            
             }
            • 3. Re: Oracle ADF viewScope causing session size bloat
              Brian Fry-Oracle
              Hi,

              Can you please also add to this thread what version of ADF and what version of WLS you are using?

              -- Brian
              • 4. Re: Oracle ADF viewScope causing session size bloat
                452071
                Hi there,

                I tried to reproduce the problem on JDeveloper 11gR2 ( 11.1.2.3) but after 5 mins of going back and fort, my server didn't crash. I did however make some changes to your code. Can you try to reproduce using the following minor change?

                Instead of
                /** Method for redirecting to page two. */
                public void goToPageTwo()
                {
                try
                {
                FacesContext.getCurrentInstance().getExternalContext.redirect("pageTwo.jspx");
                }
                catch (IOException e)
                {
                e.printStackTrace();
                }
                }

                I changed it to
                public String button_action() {
                return "goPage1";
                }

                where "goPage1" is a control flow case I defined on my unbounded taskflow.

                Thanks,

                Juan Camilo
                • 5. Re: Oracle ADF viewScope causing session size bloat
                  Frank Nimphius-Oracle
                  Hi,

                  I think Juan hits nail with his observation. If you simply redirect to a new page without adding the task flow state token (see: https://blogs.oracle.com/jdevotnharvest/entry/how_to_efficiently_redirect_an) then the ADF controller is out of the loop in the sense of garbage collection the mananaged bean and basically waits for a timeout. Generally speaking, traditional web navigation models using redirects are sub-optimal in JavaServer Faces unless you manage the state yourself in tokens so you can clean up session state or use request scopes.

                  Frank
                  • 6. Re: Oracle ADF viewScope causing session size bloat
                    974220
                    Thanks for all your replies. I'm using ADF 11gR1 (11.1.1.2.0) and WebLogic server 10.3.4.0. I'll update this thread with some results once I've tried the different redirect method.

                    Edited by: 971217 on 16-Nov-2012 01:00
                    • 7. Re: Oracle ADF viewScope causing session size bloat
                      974220
                      I'm pleased to report that Jaun is correct - I can observe the heap being correctly reclaimed if I make the following changes

                      adfc-config.xml
                      <view id="pageOne">
                          <page>/pageOne.jspx</page>
                      </view>
                      <view id="pageTwo">
                          <page>/pageTwo.jspx</page>
                      </view>
                      PageOneBB.java (plus a corresponding change to PageTwoBB.java - I'm sure you can figure that out yourself!)
                      /** Method for redirecting to page two. */
                      public void goToPageTwo()
                      {
                          FacesContext facesContext = FacesContext.getCurrentInstance();
                      
                          ExternalContext externalContext = facesContext.getExternalContext();
                      
                          String viewId = "pageTwo";
                      
                          ControllerContext controllerContext = ControllerContext.getInstance();
                      
                          String activityUrl = controllerContext.getGlobalViewActivityURL(viewId);
                      
                          try
                          {
                              externalContext.redirect(activityUrl);
                          }
                          catch (IOException e)
                          {
                              e.printStackTrace();
                          }
                      }
                      Many thanks to Jaun and Frank for helping with this. I'm going to be making the corresponding changes to our application today (there were a few instances of where we were doing a redirect without using the ControllerContext, and a few instances where we were doing it correctly!), and we're going to run a performance test over the weekend. We'll have the final results on Monday, but I'm confident that Jaun has diagnosed the problem correctly.