简体   繁体   English

Springboot Maven 项目的垃圾收集永远不会运行

[英]Garbage Collection Never Runs for Springboot Maven Project

I have a Springboot Maven project that uses a @JmsListener to read messages from a queue.我有一个 Springboot Maven 项目,它使用 @JmsListener 从队列中读取消息。

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.如果我在接收器方法的末尾添加 System.gc() 垃圾收集器正在按预期工作。 But this is definately not good practice.但这绝对不是好的做法。

How can I ensure that gc will runn at appropriate times.我如何确保 gc 将在适当的时间运行。 Any help would be greatly appreciated!任何帮助将不胜感激!

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");
        }
    }

Why?为什么? Because the JVM decided (based on its heuristics) that it's not yet the time to run.因为JVM决定(基于其启发式)现在还不是运行的时候。 When is the time to run though, depends on heap size and GC algorithm.什么时候运行,取决于堆大小和 GC 算法。 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.通常,运行 GC 循环绝不是免费操作——它至少需要 GC 循环 + 停止应用程序一段时间(称为stop-the-world事件)。 As such, GC algorithms run when they need to.因此,GC 算法会在需要时运行。

When you are using a concurrent collector ( ZGC or Shenandoah for example), it does not matter too much if they run or not;如果您使用的是并发收集器( ZGCShenandoah例如),它并没有太大的关系,如果他们运行与否; 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).它们确实有stop-the-world暂停 - 但这些非常小(例如在某些情况下与G1GC不同)。 Because of this concurrency they can be forced to run "every X seconds";由于这种并发性,它们可以被迫“每 X 秒”运行一次; Shenandoah has -XX:ShenandoahGuaranteedGCInterval=10000 (we use this in production). Shenandoah-XX:ShenandoahGuaranteedGCInterval=10000 (我们在生产中使用它)。

But I assume you are using G1GC (ie this is what you get if you do not enable a GC at all).但我假设您正在使用G1GC (即,如果您根本不启用 GC,您会得到这样的结果)。 This particular GC is mostly concurrent and is generational .这个特殊的 GC主要是并发的并且是分代的 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.年轻区域在STW暂停下收集,而Full GC (收集旧区域)主要是并发的:它可以将STW暂停延长到几分钟,字面意思,但这不是一般情况。

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.因此,当您使用G1GC ,当所有年轻的 Eden 区域(年轻区域在 Eden 和 Survivor 中进一步拆分)已满时,将触发年轻 GC 循环。 A Full GC cycle will be triggered when one of the 3 happens :当以下 3 种情况之一发生时,将触发 Full GC 循环:

 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.但这对于G1GC何时发生GC cycle是相当简单G1GC完整的图,主要是因为这 3 个中的任何一个实际上都会触发mark阶段(整个 Full GC 的某个部分),这将决定下一步做什么关于它从区域收集的数据。 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).它通常会立即触发一个年轻的 GC然后是一个mixed Collection ,但可能会选择不同的路径(同样,基于 GC 拥有的数据)。

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.因此,总的来说,在大多数情况下,您对启动GC cycle的堆压力太小——这正是您想要的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM