Ръководство за System.gc ()

1. Общ преглед

В този урок ще проучим метода System.gc () , намиращ се в пакета java.lang .

Изричното извикване на System.gc () е известно като лоша практика. Нека се опитаме да разберем защо и дали има случаи на използване, когато извикването на този метод може да бъде полезно.

2. Събиране на боклук

Виртуалната машина Java решава да извърши събирането на боклука, когато има индикации за това. Тези индикации се различават при прилагането на GC до друго. Те се основават на различни евристики. Има обаче няколко момента, когато GC ще бъде изпълнен със сигурност:

  • Ново поколение (Tenured space) е пълно, което предизвиква незначителни GC
  • Старо поколение (Eden + Survivor0 + Survivor1 пространства) е пълно, което задейства основни / пълни GC

Единственото нещо, което е независимо от изпълнението на GC, е допустимостта на обекта да се събира боклук.

Сега ще разгледаме самия метод System.gc () .

3. System.gc ()

Извикването на метода е просто:

System.gc()

Официалната документация на Oracle гласи, че:

Извикването на метода gc предполага , че Java Virtual Machine изразходва усилия за рециклиране на неизползвани обекти, за да направи паметта, която заема в момента, достъпна за бързо повторно използване.

Няма гаранция, че действителният GC ще бъде задействан .

System.gc () задейства основен GC. Следователно, съществува риск да прекарате известно време във фазата на спиране на света, в зависимост от внедряването на вашия боклук. В резултат на това имаме ненадежден инструмент с потенциално значително наказание за ефективност .

Наличието на изрично извикване на сметосъбирането трябва да бъде сериозен червен флаг за всички.

Можем да попречим на System.gc () да извършва каквато и да е работа, като използваме флага -XX: DisableExplicitGC JVM.

3.1. Настройка на производителността

Заслужава да се отбележи, че точно преди да хвърли OutOfMemoryError, JVM ще изпълни пълен GC. Следователно изричното извикване на System.gc () няма да ни спаси от неуспех .

Събирачите на боклук в днешно време са наистина умни. Те имат всички знания за използването на паметта и други статистически данни, за да могат да вземат правилни решения. Следователно трябва да им се доверим.

В случай на проблеми с паметта, имаме куп настройки, които можем да променим, за да настроим нашето приложение - като започнем от избора на различен събирач на боклук, през задаването на желаното съотношение време на приложението / GC време и накрая, завършвайки с задаване на фиксирани размери за сегментите на паметта.

Съществуват и начини за смекчаване на ефектите от Full GC, причинени от изрично повикване. Можем да използваме един от флаговете:

-XX:+ExplicitGCInvokesConcurrent

или:

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

Ако наистина искаме приложението ни да работи правилно, трябва да решим истинския основен проблем с паметта.

В следващата глава ще видим практически пример, когато изричното извикване на System.gc () изглежда полезно.

4. Пример за употреба

4.1. Сценарий

Нека напишем тестово приложение. Искаме да намерим ситуация, когато извикването на System.gc () може да е полезно .

Незначителното събиране на боклука се случва по-често от основното. Така че, вероятно трябва да се съсредоточим върху последното. Един обект се премества в ограничено пространство, ако е „оцелял“ в няколко колекции и все още е достъпен от GC корени.

Нека си представим, че имаме огромна колекция от предмети, които са живи от известно време. След това в един момент изчистваме колекцията от обекти. Може би е добър момент да стартирате System.gc () ?

4.2. Демо приложение

Ще създадем просто конзолно приложение, което ще ни позволи да симулираме този сценарий:

public class DemoApplication { private static final Map cache = new HashMap(); public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { final String next = scanner.next(); if ("fill".equals(next)) { for (int i = 0; i < 1000000; i++) { cache.put(randomUUID().toString(), randomUUID().toString()); } } else if ("invalidate".equals(next)) { cache.clear(); } else if ("gc".equals(next)) { System.gc(); } else if ("exit".equals(next)) { System.exit(0); } else { System.out.println("unknown"); } } } }

4.3. Стартиране на демонстрацията

Нека стартираме нашето приложение с няколко допълнителни флага:

-XX:+PrintGCDetails -Xloggc:gclog.log -Xms100M -Xmx500M -XX:+UseConcMarkSweepGC

Първите два флага са необходими за регистриране на GC информация. Следващите два флага задават първоначален размер на купчината и след това максимален размер на купчината. Искаме да поддържаме размера на купчината нисък, за да принудим GC да бъде по-активен. И накрая, решаваме да използваме CMS - Concurrent Mark and Sweep collector. Време е да стартираме нашето приложение!

Първо, нека се опитаме да запълним ограниченото пространство . Въведете запълване.

Можем да проучим нашия файл gclog.log, за да видим какво се е случило. Ще видим около 15 колекции. Линията, регистрирана за единични колекции, изглежда така:

197.057: [GC (Allocation Failure) 197.057: [ParNew: 67498K->40K(75840K), 0.0016945 secs] 168754K->101295K(244192K), 0.0017865 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] secs]

Както виждаме, паметта е запълнена.

След това нека принудим System.gc (), като напишем gc . Виждаме, че използването на паметта не се е променило значително:

238.810: [Full GC (System.gc()) 238.810: [CMS: 101255K->101231K(168352K); 0.2634318 secs] 120693K->101231K(244192K), [Metaspace: 32186K->32186K(1079296K)], 0.2635908 secs] [Times: user=0.27 sys=0.00, real=0.26 secs]

After a few more runs, we'll see that memory size stays at the same level.

Let's clear the cache by typing invalidate. We should see no more log lines appear in the gclog.log file.

We can try to fill cache a few more times, but no GC is happening. This is a moment when we can outsmart the garbage collector. Now, after forcing GC, we'll see a line like:

262.124: [Full GC (System.gc()) 262.124: [CMS: 101523K->14122K(169324K); 0.0975656 secs] 103369K->14122K(245612K), [Metaspace: 32203K->32203K(1079296K)], 0.0977279 secs] [Times: user=0.10 sys=0.00, real=0.10 secs]

We've released an impressive amount of memory! But was it really necessary right now? What happened?

According to this example, calling System.gc() might seem tempting when we're releasing big objects or invalidating caches.

5. Other Usages

There are very few reasons when an explicit call to the System.gc() method might be useful.

One possible reason is cleaning memory after server startup — we're starting a server or application which does a lot of preparation. After that, there are a lot of objects to be finalized. However, cleaning after such preparation shouldn't be our responsibility.

Another is memory leak analysisit's more a debugging practice than something we would like to keep in the production code. Calling System.gc() and seeing heap space still being high might be an indication of a memory leak.

6. Summary

In this article, we investigated the System.gc() method and when it might seem useful.

Никога не трябва да разчитаме на него, когато става въпрос за коректността на нашето приложение. GC в повечето случаи е по-умен от нас и в случай на проблеми с паметта, трябва да помислим за настройка на виртуалната машина, вместо да правим такова изрично повикване.

Както обикновено, кодът, използван в тази статия, може да бъде намерен в GitHub.