Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Garbage Collection Never Runs for Springboot Maven Project

Tags:

I have a Springboot Maven project that uses a @JmsListener to read messages from a queue.

If no events are coming in, the heap memory slowly increases. When messages are coming the heap memory is ramping up fast. But the heap memory never comes down (check image below).

If I add System.gc() atthe end of the receiver method The garbage collector is doing its job as expected. But this is definately not good practice.

How can I ensure that gc will runn at appropriate times. Any help would be greatly appreciated!

Heap Memory Usage

Heap Memory Usage

Receiver method

@JmsListener(destination = "${someDestination}", containerFactory = "jmsListenerContainerFactory")
    public void receiveMessage(Message message){
        if (message instanceof BytesMessage) {
            try {
                List<Trackable> myList;

                BytesMessage byteMessage = (BytesMessage) message;
                byte[] byteData = new byte[(int) byteMessage.getBodyLength()];
                byteMessage.readBytes(byteData);

                DocumentBuilder dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
                Document doc = dBuilder.parse(new InputSource(new StringReader(new String(byteData))));

                TransformerFactory factory = TransformerFactory.newInstance();
                factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
                Transformer transformer = factory.newTransformer();

                StringWriter writer = new StringWriter();
                transformer.transform(new DOMSource(doc.getElementsByTagName(SOME_TAG_NAME).item(0)), new StreamResult(writer));
                String outputXmlString = writer.getBuffer().toString();

                XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
                XMLStreamReader xmlReader = xmlFactory.createXMLStreamReader(new StringReader(outputXmlString));

                JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class);

                MyEvent myEvent = ((JAXBElement<MyEvent>) jaxbContext.createUnmarshaller().unmarshal(xmlReader)).getValue();
                myList = myService.saveEvent(myEvent);

                LOGGER.info(String.format("Received message with EventID: %s and successfully inserted into database", myEvent.getID()));

            } catch (Exception e) {
                LOGGER.error(e.getClass().getCanonicalName() + " in Receiver: ", e);
            }
        } else {
            LOGGER.error("Received unsupported message format from MQ");
        }
    }
like image 810
Niels Ferguson Avatar asked Jan 29 '20 09:01

Niels Ferguson


1 Answers

Why? Because the JVM decided (based on its heuristics) that it's not yet the time to run. When is the time to run though, depends on heap size and GC algorithm. Generally, running a GC cycle is, by no means, a free operation - it requires GC cycles + stopping your application for some period of time (called stop-the-world events), at least. As such, GC algorithms run when they need to.

When you are using a concurrent collector (ZGC or Shenandoah for example), it does not matter too much if they run or not; this is because they are concurrent: they run while your application is running. They do have stop-the-world pauses - but these are very small (unlike G1GC for example in certain cases). Because of this concurrency they can be forced to run "every X seconds"; Shenandoah has -XX:ShenandoahGuaranteedGCInterval=10000 (we use this in production).

But I assume you are using G1GC (i.e. this is what you get if you do not enable a GC at all). This particular GC is mostly concurrent and is generational. It splits the heap in young and old regions and collects them independently. Young regions are collected under a STW pause, while a Full GC (collects old regions) is mostly concurrent: it can stretch a STW pause to minutes, literally, but it's not the general case.

So, when you use G1GC, a young GC cycle will be triggered when all young Eden regions (young regions are split in Eden and Survivor further) are full. A Full GC cycle will be triggered when one of the 3 happens :

 1) IHOP is reached 
 2) G1ReservePercent is reached 
 3) a humongous allocation happens (an allocation that spans across multiple regions - think huge Objects). 

But this is rather a simplistic and somehow incomplete picture of when a GC cycle happens for G1GC, mainly because any of those 3 will actually trigger a mark phase (a certain portion of the entire Full GC), that will decide what to do next based on the data it gathers from regions. It usually triggers a young GC immediately and then a mixed Collection, but may be a different path is selected (again, based on the data that a GC has).

So overall, you have too little pressure on the heap for a GC cycle to be initiated and most of the time - this is exactly what you want.

like image 125
Eugene Avatar answered Sep 30 '22 21:09

Eugene