eclipse-ee4j / jaxb-ri

Jaxb RI
https://eclipse-ee4j.github.io/jaxb-ri/
BSD 3-Clause "New" or "Revised" License
205 stars 111 forks source link

Some UnmarshalExceptions result in memory leaks - reference to object never released by com.sun.xml.bind.v2.runtime.unmarshaller.Scope #932

Open Tomas-Kraus opened 11 years ago

Tomas-Kraus commented 11 years ago

I have found that certain UnmarshalExceptions result in memory leaks. A reference to the unmarshalled object is held by com.sun.xml.bind.v2.runtime.unmarshaller.Scope and is never released for the lifetime of the application, which eventually results in an OutOfMemoryError if a sufficient number of UnmarshalExceptions occur.

Stacktrace of such an exception that results in a leak:

javax.xml.bind.UnmarshalException
 - with linked exception:
[org.xml.sax.SAXParseException: XML document structures must start and end within the same entity.]
        at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:315)
        at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:527)
        at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:224)
        at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:190)
        at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:137)
        at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:194)
        at JaxbLeakTest.main(JaxbLeakTest.java:21)

Reduced test case:

import java.io.StringReader;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

public class JaxbLeakTest {
    public static void main(String args[]) throws Exception {
        JAXBContext context = JAXBContext.newInstance(JaxbLeakTest.Foo.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        for (int i = 0; i < 1000; i++) {
            if (i % 100 == 0) {
                Runtime.getRuntime().gc();
                System.out.println("Foo.getInstanceCount()=" + Foo.getInstanceCount());
            }
            try {
                unmarshaller.unmarshal(new StringReader("<foo><bar/>"));
            } catch (Exception e) {
            }
        }
    }

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlRootElement(name = "foo")
    public static class Foo {
        private List<String> bar;

        private static AtomicInteger instanceCount = new AtomicInteger();

        public Foo() {
            instanceCount.incrementAndGet();
        }

        protected void finalize() {
            instanceCount.decrementAndGet();
        }

        public static int getInstanceCount() {
            return instanceCount.get();
        }
    }
}

The output of the above test case, using JAXB RI 2.2.6:

Foo.getInstanceCount()=0
Foo.getInstanceCount()=100
Foo.getInstanceCount()=200
Foo.getInstanceCount()=300
Foo.getInstanceCount()=400
Foo.getInstanceCount()=500
Foo.getInstanceCount()=600
Foo.getInstanceCount()=700
Foo.getInstanceCount()=800
Foo.getInstanceCount()=900

In the process of creating the above test case, I found that the type of the variable "bar" has an effect on whether the leak occurs. The leak only seems to occur when bar is a List.

Reference chain from Eclipse:

[1] JaxbLeakTest$Foo  (id=26)
  '[1]' referenced from:
    [0] Scope<BeanT,PropT,ItemT,PackT>  (id=158)
      '[0]' referenced from:
        [0] Scope<BeanT,PropT,ItemT,PackT>[128]  (id=137)
          '[0]' referenced from:
            [1] UnmarshallingContext  (id=133)

Affected Versions

[2.1.10, 2.2.6]

Tomas-Kraus commented 6 years ago
Tomas-Kraus commented 11 years ago

@glassfishrobot Commented Reported by mryan

Tomas-Kraus commented 8 years ago

@glassfishrobot Commented peddapola said: instead caching Unmarshaller, just cache JAXBContext

below code should work fine, without much performance impact:

import java.io.StringReader; import java.util.List; import java.util.concurrent.atomic.AtomicInteger;

import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement;

public class JaxbLeakTest { public static void main(String args[]) throws Exception { JAXBContext context = JAXBContext.newInstance(JaxbLeakTest.Foo.class); for (int i = 0; i < 1000; i++) { Unmarshaller unmarshaller = context.createUnmarshaller();

if (i % 100 == 0)

{ Runtime.getRuntime().gc(); System.out.println("Foo.getInstanceCount()=" + Foo.getInstanceCount()); }

try

{ unmarshaller.unmarshal(new StringReader("")); }

catch (Exception e) { } } }

@XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "foo") public static class Foo { private List bar;

private static AtomicInteger instanceCount = new AtomicInteger();

public Foo()

{ instanceCount.incrementAndGet(); }

protected void finalize()

{ instanceCount.decrementAndGet(); }

public static int getInstanceCount()

{ return instanceCount.get(); }

} }

Tomas-Kraus commented 11 years ago

@glassfishrobot Commented File: JaxbLeakTest.java Attached By: mryan

Tomas-Kraus commented 11 years ago

@glassfishrobot Commented Was assigned to yaroska

Tomas-Kraus commented 7 years ago

@glassfishrobot Commented This issue was imported from java.net JIRA JAXB-932