垃圾收集从不为 Spring Boot Maven 项目运行

Garbage Collection Never Runs for Springboot Maven Project

我有一个 Springboot Maven 项目,它使用 @JmsListener 从队列中读取消息。

如果没有事件进入,堆内存会缓慢增加。当消息到来时,堆内存正在快速增加。但是堆内存永远不会下降(检查下图)。

如果我在接收方方法的末尾添加 System.gc() 垃圾收集器正在按预期执行其工作。但这绝对不是好的做法。

如何确保 gc 在适当的时间运行。任何帮助将不胜感激!

堆内存使用

接收方方法

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

为什么?因为 JVM 决定(基于它的启发式)现在还不是 运行 的时候。不过,什么时候 运行 取决于堆大小和 GC 算法。一般来说,运行 GC 循环绝不是免费操作 - 它至少需要 GC 循环 + 停止您的应用程序一段时间(称为 stop-the-world 事件)。因此,GC 算法 运行 在需要的时候。

当您使用并发收集器(例如ZGCShenandoah)时,没关系 运行 或不;这是因为它们 并发 :它们 运行 而您的应用程序 运行ning。他们确实有 stop-the-world 停顿 - 但这些停顿非常小(例如在某些情况下与 G1GC 不同)。由于这种并发性,它们 可以 被迫 运行 "every X seconds"; Shenandoah-XX:ShenandoahGuaranteedGCInterval=10000(我们在生产中使用它)。

但我假设您使用的是 G1GC(即如果您根本不启用 GC,这就是您得到的结果)。这个特定的 GC 大部分并发并且是世代。它将堆拆分为 youngold 区域并独立收集它们。年轻区域在 STW 暂停下收集,而 Full GC(收集旧区域)主要是 并发:它可以延长 STW 暂停到分钟,从字面上看,但这不是一般情况。

所以,当你使用G1GC时,当所有个年轻的伊甸园区域(年轻的区域在伊甸园和幸存者进一步分裂)时,将触发年轻的GC周期满的。当发生以下 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). 

但这对于 G1GC 何时发生 GC cycle 是相当简单且不完整的描述,主要是因为这 3 个中的任何一个实际上都会触发 mark 阶段(某个整个 Full GC 的一部分),它将根据从区域收集的数据决定下一步做什么。它通常会立即触发 young GCthen mixed Collection,但可能会选择不同的路径(同样,基于GC 拥有的数据)。

总的来说,堆上的压力太小,无法启动 GC cycle,而且大多数时候 - 这正是您想要的。