Основни принципи за настройка на Garbage Collection от нулата
В тази статия не бих искал да се фокусирам върху принципа на събирача на отпадъци - това е красиво и ясно описано тук: habrahabr.ru/post/112676/. Бих искал да премина към практическите основи и количествените характеристики на настройката на Garbage Collection в JVM - и да се опитам да разбера колко ефективно може да бъде то.
Количествени характеристики на оценката на ефективността на GC
Обърнете внимание на следните показатели:
- Широчина на честотната лентаМярка, която определя способността на дадено приложение да работи при пиково натоварване, независимо от паузите по време на изграждане и количеството необходима памет
- Време за реакцияGC мярка, която измерва способността на приложението да се справи с броя GC спирания и колебания
- Количество използвана паметКоличество памет, необходимо за ефективна работа на GC
По правило изброените характеристики са компромиси и подобряването на една от тях води до разходи за останалите. За повечето приложения и трите характеристики са важни, но често една или две са по-важни за приложението - това ще бъде отправната точка при настройката.
Основи на GC Tuning
Обмислете три основни фундаментални правила за разбиране на настройката на GC:
- Необходимо е да се стремим да гарантираме, че максимален брой обекти се почистват по време на работа на малък GC (събиране на незначителен боклук). Този принцип ви позволява да намалите броя и честотата на пълния колектор за боклук (пълно събиране на боклук) - чиято работа е основната причина за големи забавяния в приложението
- Колкото повече памет е разпределена за дадено приложение, толкова по-добре работи събирането на отпадъци и толкова по-добри показатели за пропускателна способност и време за реакция се постигат.
- Ефективенсамо 2 от 3 количествени характеристики могат да бъдат конфигурирани - пропускателна способност, време за реакция, размер на разпределената памет - ефективната стойност на размера на необходимата памет се разбира като нейното минимизиране
Ще проведем експеримента върху:
За които режимът е активиран по подразбиране - сървър и UseParallelGC (многопоточна фаза на събиране на малък боклук)
За да оцените общия размер на паузата на събирача на отпадъци, можете да стартирате в режим:
И обобщете забавянето според дневника на gc.log:
Където реално = 0,01 секунди е реалното време, изразходвано за изграждане.
Или можете да използвате помощната програма VisualVm с инсталиран плъгин VisualGC, в който можете ясно да наблюдавате разпределението на паметта в различни области на GC (Eden, Survivor1, Survivor2, Old) и да видите статистика за началото и продължителността на събирането на боклука.
Определяне на необходимото количество памет
Като начало трябва да стартираме приложението с възможно най-много памет, отколкото приложението действително се нуждае. Ако първоначално не знаем колко памет ще заема нашето приложение, можем да стартираме приложението без да указваме -Xmx и -Xms и HotSpot VM сама ще избере размера на паметта. Ако при стартиране на приложението получим OutOfMemory(Java heap space или PermGen space), тогава можем итеративно да увеличим размера на наличната памет (-Xmx или -XX:PermSize), докато грешките изчезнат. Следващата стъпка е да се изчисли размерът на дълготрайните живи данни - това е размерът на старите и постоянните области на купчината след пълната фаза на събиране на отпадъци. Този размер е приблизителното количество памет, необходимо за функционирането на приложението, за да го получите, можете да погледнете размера на областите след серия от пълни компилации. По правило размерът на необходимата памет за приложението -Xms и -Xmx е 3-4 пъти по-голям от обема на liveданни. И така, за регистрационния файл по-горе стойността на старата област след пълната фаза на събиране на отпадъци е 349363K. Тогава предложената стойност е -Xmx и -Xms
1400 MB. -XX:PermSize и -XX:MaxPermSize - 1,5 пъти по-големи от PermGenSize след пълна фаза на събиране на отпадъци - 13324K
20 MB. Размерът на младото поколение се приема равен на 1-1,5 от размера на обема живи данни
525 MB. След това получаваме реда за стартиране на jvm със следните параметри:
Във VisualVm получаваме следната картина:
Само за 30 секунди от експеримента са направени 54 сглобки - 31 малки и 23 пълни - с общо време на спиране от 3,227 s. Това забавяне може да не отговаря на необходимите изисквания - нека видим дали можем да подобрим ситуацията, без да променяме кода на приложението.
Задаване на допустимото време за реакция
Следните параметри трябва да бъдат измерени и взети предвид при настройване на времето за реакция:
- Измерване на продължителността на събиране на малък боклук
- Измерване на честотата на събиране на малък боклук
- Измерване на продължителността на пълното събиране на отпадъци в най-лошия случай
- Измерване на честотата на най-лошия случай на пълно събиране на отпадъци
Регулиране на размера на младото и старото поколение
Времето, необходимо за изпълнение на фазата на събиране на малък боклук, зависи пряко от броя на обектите в младото поколение, колкото по-малък е неговият размер, толкова по-кратка е продължителността, но в същото време честотата се увеличава, т.к. зоната започва да се запълва по-често. Нека се опитаме да намалим времето на всяко малко събрание, като намалим размера на младото поколение, като същевременно запазим размера на старото поколение. Може да се изчисли приблизително, че всяка секунда трябва да изчистим 50 потока * 8 обекта * 1 Mb в младото поколение
400Mb. Нека стартираме с параметри:
Във VisualVm получаваме следната картина:
Ние нямаме ефект върху общото време за работа на събиране на малък боклукуспя - 1.533s - честотата на малки събирания се увеличи, но общото време се влоши - 3.661 поради факта, че скоростта на запълване на старото поколение се увеличи и честотата на извикване на пълното събиране на боклука се увеличи. За да преодолеем това - нека се опитаме да увеличим размера на старото поколение - стартирайте jvm с параметрите:
Общата пауза вече е подобрена и е 2,637 s, докато общата стойност на паметта, необходима за приложението, е намаляла - по този начин, итеративно, можете да намерите правилния баланс между старото и младото поколение, за да разпределите живота на обектите в конкретно приложение.
Ако времето за забавяне все още не ни устройва, можем да преминем към паралелния събирач на отпадъци, като активираме опцията -XX: + UseConcMarkSweepGC - алгоритъм, който ще се опита да свърши основната работа по маркиране на обекти за изтриване в отделна нишка, паралелно с нишките на приложението.
Конфигуриране на паралелния събирач на отпадъци
ConcMarkSweep GC изисква по-внимателна настройка - една от основните цели е да се намали броят на паузите за спиране на света при липса на достатъчно място в старото поколение за местоположението на обектите - т.к. тази фаза отнема средно повече време от пълната фаза на събиране на боклука на пропускателния GC. В резултат на това продължителността на събирането на боклука в най-лошия случай може да се увеличи и трябва да се избягват честите преливания на старо поколение. Като правило - при преминаване към ConcMarkSweep GC се препоръчва да увеличите размера на старото поколение с 20-30% - стартирайте jvm със следните параметри:
Общата пауза беше намалена до 1,923 s.
Корекция на размера на Survivor
Под графиката можете да видите разпределението на паметта на приложенията според броя на преходите между етапите Eden, Survivor1 и Survivor2, преди да влязат в старото поколение. Въпросът е, че един от начинитенамаляване на броя на препълванията на старо поколение в ConcMarkSweep GC - предотвратяване на директен поток на обекти от младо поколение директно към старо - заобикаляйки зоната на оцелелите.
Можете да стартирате jvm с опцията -XX:+PrintTenuringDistribution, за да наблюдавате разпределението на обектите между етапите. В gc.log можем да наблюдаваме:
Общият размер на оцелелите обекти е 40900584, CMS по подразбиране използва 50% бариера за запълване на зоната на оцелелите. Така получаваме размера на площта
80 MB. При стартиране на jvm той се задава от параметъра -XX:SurvivorRatio, който се определя от формулата:
Искайки да оставим размера на eden space същия, получаваме:
Разпределението се е подобрило, но общото време не се е променило много поради спецификата на приложението, факт е, че след често събиране на малки отпадъци, размерът на оцелелите обекти винаги е по-голям от наличния размер на зоните за оцеляване, така че в нашия случай можем да пожертваме правилното разпределение в името на размера на пространството на eden:
В резултат на това успяхме да намалим размера на общата пауза от 3,227 s до 1,481 s за 30 s от експеримента, като същевременно леко увеличихме общата консумация на памет. Много или малко - зависи от спецификата, по-специално, предвид тенденцията към намаляване на цената на физическата памет и принципа за максимизиране на използваната памет - все още е важно да се намери баланс между различните области на GC и този процес е по-креативен, отколкото научен.