Сборка мусора никогда не запускается для Springboot Maven Project - PullRequest
4 голосов
/ 29 января 2020

У меня есть проект Springboot Maven, который использует @JmsListener для чтения сообщений из очереди.

Если события не поступают, память кучи медленно увеличивается. Когда приходят сообщения, объем динамической памяти быстро увеличивается. Но память кучи никогда не падает (проверьте изображение ниже).

Если я добавлю System.g c () в конце метода получателя Сборщик мусора выполняет свою работу, как ожидалось. Но это определенно не хорошая практика.

Как я могу гарантировать, что g c будет работать в подходящее время. Любая помощь будет принята с благодарностью!

Использование памяти кучи

Heap Memory Usage

Метод получателя

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

1 Ответ

3 голосов
/ 29 января 2020

Почему? Потому что JVM решил (исходя из своей эвристики), что еще не время бежать. Время выполнения зависит от размера кучи и алгоритма G C. Как правило, выполнение цикла G C ни в коем случае не является бесплатной операцией - для этого требуется G C циклов + остановка приложения на некоторый период времени (называемый stop-the-world событиями), по крайней мере. Таким образом, алгоритмы G C запускаются тогда, когда это необходимо.

Когда вы используете параллельный коллектор (например, ZGC или Shenandoah), не имеет значения слишком много, если они работают или нет; это потому, что они одновременны : они работают во время работы вашего приложения. У них есть stop-the-world паузы - но они очень малы (в отличие от G1GC, например, в некоторых случаях). Из-за этого параллелизма они могут быть вынуждены запускать "каждые X секунд"; Shenandoah имеет -XX:ShenandoahGuaranteedGCInterval=10000 (мы используем это в производстве).

Но я предполагаю, что вы используете G1GC (то есть это то, что вы получите, если вообще не включите G C) , Этот конкретный G C является в основном одновременным и поколенческим . Он разбивает кучу в молодых и старых регионах и собирает их независимо. Молодые регионы собираются с STW паузой, в то время как Full GC (собирает старые регионы) - в основном одновременно: может буквально растягивать STW паузу до минут, но это не общий случай .

Итак, когда вы используете G1GC, молодой цикл G C будет запущен, когда все молодые районы Эдема (молодые регионы разделены на Эдем и Оставшийся в живых далее) заполнены. , Полный цикл G C будет запущен, когда произойдет одно из 3:

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

Но это довольно упрощенная c и как-то неполная картина, когда GC cycle происходит для G1GC главным образом потому, что любой из этих 3 фактически вызовет фазу mark (определенную часть всей полной G C), которая будет решать, что делать дальше, основываясь на данных, которые она собирает из регионов. Обычно он сразу запускает young G C и , затем a mixed Collection, но может быть выбран другой путь (опять же, на основании данных, которые G C has).

В общем, у вас слишком мало давления на кучу для запуска GC cycle, и большую часть времени - это именно то, что вы хотите.

...