* Garbage Collection и метод finalize()
Публикувано на 15 октомври 2009 в раздел ПИК-3 Java.
В програмният език C++ съществува понятието “деструктор” – метод който “почиства” преди даден обект да бъде унищожен. Такъв метод е често използван там, защото в C++ програмистът сам контролира кога един обект да бъде изтрит (чрез “обратния” на new оператор delete).
В Java програмистът няма контрол над унищожението на обектите. Цялата тежест над това е прехвърлена върху т.нар. Garbage Collector. Въпреки това има аналогия на “почистващ метод” (деструктор) – това е метод “finalize()”. Именно той се извиква в момента, в който даден обект е изтрит от Garbage Collector.
За съжаление програмистите, които преминават от C++ към Java ще забележат бързо, че Garbage Collector не се извиква често. При по-малките програми даже е възможно да не бъде извикан въобще. Ето ви един пример:
public class GCExample{
public static void main(String[] args){
User[] u = new User[200];
for (int i=0; i<u.length; i++){
u[i] = new User();
}
System.out.println("Users online: "+User.count);
System.out.println("Last id: "+User.lastid);
// "Изтриваме" 10ти елемент
u[10] = null;
System.out.println("Users online: "+User.count);
System.out.println("Last id: "+User.lastid);
}
}
class User{
public static int count = 0;
public static int lastid = 0;
public int id;
public User(){
this.count++;
this.lastid++;
this.id = lastid;
}
protected void finalize(){
count--;
}
}
Освен ако нямате завиден късмет Garbage Collector да се е включил, то резултатът ще бъде следния:
Users online: 200 Last id: 200 Users online: 200 Last id: 200
Виждате, че finalize() методът не е извикан въобще. Това е защото Garbage Collector не се е стартирал все още. Честно казано при тази програма той не би трябвало да се включи въобще. Опитайте чрез добавянето на следния код:
while(User.count == 200){}
System.out.println("Garbage Collection was triggered!");
Програмата почти 100% сигурно ще зацикли и никога няма да спре. Ако обаче ние собственоръчно предизвикаме Garbage Collector да се включи, то ще видим, че всичко е наред:
public class GCExample{
public static void main(String[] args){
User[] u = new User[200];
for (int i=0; i<u.length; i++){
u[i] = new User();
}
System.out.println("Users online: "+User.count);
System.out.println("Last id: "+User.lastid);
// "Изтриваме" 10ти елемент
u[10] = null;
System.gc();
System.runFinalization();
System.out.println("Users online: "+User.count);
System.out.println("Last id: "+User.lastid);
}
}
Тук резултатът категорично сигурно ще бъде:
Users online: 200 Last id: 200 Users online: 199 Last id: 200
Метод System.gc() извиква Garbage Collector, а System.runFinalization() го кара да почисти абсолютно всичко. Метод finalize() се е изпълнил и е намалил променливата usersonline с единица – точно това, което желаехме. Сега трябва да отговорим на въпроса “защо Garbage Collector не се включи в предишния пример” и по-скоро “кога въобще се включва Garbage Collector сам”.
Отговорът е, че Garbage Collector се включва само и единствено тогава, когато е нужно освобождаване на памет. С други думи – ако JVM изпита недостиг на памет, то тя си освобождава такава, като почиства “боклука”. Ето един пример, с който ще демонстрираме това:
public class GCExample{
public static void main(String[] args){
while (GCExample.continueFlag){
new GCExample();
}
// "приспиваме програмата за 2 секунди
try{
Thread.sleep(2000);
}
catch(Exception e){}
System.out.println(GCExample.ObjectsCreated);
System.out.println(GCExample.ObjectsDeleted);
}
}
class GCExample{
public static boolean continueFlag = true;
public static int ObjectsCreated = 0;
public static int ObjectsDeleted = 0;
public GCExample(){
this.ObjectsCreated++;
}
protected void finalize(){
continueFlag = false;
ObjectsDeleted++;
}
}
Едно примерно изпълнение би върнало следния резултат:
18580 1917
Виждаме, че общо са се създали 18580 обекта и чак тогава се е включил Garbage Collector. Към нито един от тези създадени обекти няма насочена променлива, но въпреки това Garbage Collector е “събрал” само 1917 от тях. Нарочно “приспахме” програмата за 2 секунди, за да сме сигурни, че той си е свършил работата и не сме го прекъснали с край на програмата ни. Нека все пак демонстрираме, че System.runFinalization() работи коректно:
public class GCExample{
public static void main(String[] args){
while (GCExample.continueFlag){
new GCExample();
}
try{
Thread.sleep(2000);
}
catch(Exception e){}
System.gc();
System.runFinalization();
System.out.println(GCExample.ObjectsCreated);
System.out.println(GCExample.ObjectsDeleted);
}
}
Резултатът тук вече очаквано ще бъде нещо подобно на:
18688 18688
Първоначално бихме си казали, че “щом желаем да изтрием обект, то можем винаги да си извикваме сами System.gc() и System.runFinalization()”. Това обаче е лоша практика, понеже “събирането на боклука” всъщност е доста тежка процедура, която отнема доста ресурси – повече от процедурата на създаване на обект:
public class GCExample{
public static void main(String[] args){
long startCreateObjectsTime = System.currentTimeMillis();
while (GCExample.continueFlag){
new GCExample();
}
long endCreateObjectsTime = System.currentTimeMillis();
long startGCTime = System.currentTimeMillis();
System.gc();
System.runFinalization();
long endGCTime = System.currentTimeMillis();
System.out.println(endCreateObjectsTime - startCreateObjectsTime);
System.out.println(endGCTime - startGCTime);
}
}
Примерен резултат е:
15 94
С други думи събирането на боклука е отнело в пъти повече време отколкото създаването на обектите, които биват изтрити. Затова е редно да избягваме честото ръчно пускане на Garbage Collection. Всъщност именно концепцията за Garbage Collection и липсата на възможност за контрол над изтриване на обектите е една от най-честите критики към езика Java.
Един коментар за “Garbage Collection и метод finalize()”
Trackback URI | RSS за коментарите
Пусни коментар
Категории
- Бази от Данни (52)
- Вероятности (31)
- История (15)
- Кучета (69)
- Лада Нива (96)
- Математика (166)
- Методика (53)
- Общи работи (110)
- ПИК-3 Java (38)
- Политика (41)
- Програмни Среди (1)
- ПТСК (41)
- С/C++ (45)
- Семейни (16)
- Физика (35)
- ХHTML/JS (25)
- Храна (11)
Нови
- Извеждане на няколко произволни реда
- Full-Text търсене с InnoDB в MySQL
- Късметче от кафе
- Пред блока…
- Бушонно табло на Лада Нива
25 ноември 2009 на 18:13
Мен също ме дразни това, че не мога да си трия обектите когато искам. Затова се опитвам да си замисля концепция, при която създадените обекти ще се рециклират, в смисъл няма да се създават и инициализират нови, а на старите ще бъдат придавани нови свойства (стойности). Това не е идеално решение и вероятно ще бъде полезно в някои конкретни случаи, например когато боравим с голям брой еднотипни обекти. Това също е ограничение – например заделили сме повече от нужното памет или пък в някои моменти недостига. При всички случаи, ако работим с “твърд обем” памет, бихме могли да планираме алгоритъма да работи по-иначе, съобразно тези ограничения.