-
1. Data: 2009-07-27 08:38:18
Temat: Opowiadanie o GC
Od: Maciej Sobczak <s...@g...com>
Jest taki problem.
Program działający w systemie rozproszonym tworzy lokalnie i
przetwarza obiekty typu Item. Każdy Item ma swój unikalny
identyfikator typu long. Item zna swoje id, któro jest znane również
innym programom działającym w systemie. Te inne programy wpływają na
stan lokalnych obiektów Item na podstawie ich id.
Jeżeli program porzuci swój lokalny obiekt Item, to wszystko co
dotyczy jego id może być później zignorowane. Zwykle porzucanie
obiektów Item następuje wtedy, gdy i tak nie ma szansy na dalsze
interakcje dotyczące tego id, ale nie musi tak być.
Ponieważ przy każdym komunikacie przychodzącym z zewnątrz trzeba jakoś
znaleźć odpowiedni lokalny obiekt Item, w programie istnieje mapa,
której kluczem jest long a wartością jest Item (bądź wskaźnik na
Item), czyli która dla danego id potrafi znaleźć Item.
Z powodów, które nie są ważne w tej dyskusji, ten projekt jest pisany
zarówno w C++ jak i w Javie.
W C++ typ Item ma destruktor, który wyrejestrowuje swoje id z mapy.
Dzięki temu, jeśli program "porzuci" jakiś obiekt Item (przez co nie
może już go obserwować i reagować na zmiany jego stanu), to
odpowiadający mu wpis w mapie jest natychmiast wyrzucany. W
szczególności, jeśli program używa obiektów Item w takiej pętli:
for (...)
{
Item it;
// ...
}
to w efekcie mapa nigdy nie ma więcej, niż 1 wpis. Zużycie pamięci
jest wtedy *stałe* (albo ma znaną górną granicę) - dotyczy to również
bardziej złożonych, ale nadal cyklicznych układów.
W Javie nie ma destruktorów, ale jest GC. Jeśli program "porzuci"
jakiś obiekt Item, to w pamięci wisi zarówno obiekt Item, jak i
odpowiadający jego id wpis w mapie. Wyciek.
Na szczęście są słabe referencje, ale uwaga - Item jest *wartością* a
nie kluczem w mapie. W najlepszym razie GC posprząta obiekty Item, ale
wpisy w mapie pozostaną, przez co mapa i tak puchnie w nieskończoność.
Wyciek.
Można pomyśleć o podpięciu się pod finalizator, ale w opinii
szanowanych obywateli jest to robienie wiochy. Zamiast tego doradzono
mi zrobić *dodatkowy wątek*, który będzie okresowo jeździł po mapie i
wywalał wpisy, których wartości się wyzerowały (czyli te wpisy,
których obiekty Item zostały posprzątane przez GC).
To rozwiązanie *prawie* działa, ale ma takie dwie przykre cechy:
* Komplikuje projekt wymuszając istnienie dodatkowego wątku.
* Wprowadza dodatkowe wartości konfiguracyjne służące do strojenia
pracy tego wątku (okres skanowania).
Piszę *prawie*, bo pomiędzy posprzątaniem obiektów Item (hint: to się
dzieje dopiero wtedy, gdy GC jest wystarczająco zestresowany, żeby się
w ogóle ruszyć - co następuje akurat wtedy, gdy *zaczyna brakować
pamięci*) a skanem mapy jest pewien odstęp czasu, kiedy niepotrzebne
już wpisy w mapie *nadal* zajmują pamięć, zmniejszając efektywnie pulę
dostępnej pamięci. To powoduje, że przy dalszej pracy programu GC
szybciej będzie zestresowany i znowu coś tam posprząta, ale przez nowe
wiszące wpisy w mapie i tak zostawi jeszcze mniejszą pulę wolnej
pamięci. Ostatecznie program wylatuje na jej braku, chociaż z
projektowego punktu widzenia może nigdy nie używać więcej, niż jednego
obiektu Item.
Oczywiście zmiany okresu skanowania mapy wpływają jedynie na
*prawdopodobieństwo* poprawnego działania całego programu i nigdy nie
można tej poprawności *zagwarantować*.
Ot, taka sobie historyjka. Ale może ktoś znudzony upałami i burzami
wpadnie na jakiś pomysł, jak to poprawić.
Jedną z możliwości jest dodatnie do klasy Item funkcji close() i
uprzejme poproszenie programisty, żeby jej używał. Jest to
rozwiązanie, którego poziom abstrakcji i wartość projektowa
odpowiadają językowi C.
Są inne?
--
Maciej Sobczak * www.msobczak.com * www.inspirel.com
-
2. Data: 2009-07-27 09:30:24
Temat: Re: Opowiadanie o GC
Od: Jacek Czerwinski <...@...z.pl>
Maciej Sobczak pisze:
> Jedną z możliwości jest dodatnie do klasy Item funkcji close() i
> uprzejme poproszenie programisty, żeby jej używał. Jest to
> rozwiązanie, którego poziom abstrakcji i wartość projektowa
> odpowiadają językowi C.
No niestety, mnie najbardziej wkurzająca cecha Javy.
> Są inne?
>
1. Eclipse SWT żeby wylądować poprawnie z zasobami zewnętrznymi (zasoby
GUI które są skończone w systemie) bardzo mocno buduje relację
rodzicielstwa swoich obiektów programowych. Potem korzystając z tej
relacji niszczenie rodzica powoduje dzieci itd.
Obiekty pierwszego poziomu trzeba niestety dispose() czyli plus/minus
close(), ale jest tego sporo mniej. Oczywiście są zasady i wskazania itd...
Nie wyczułem jednak w twoim 'opowiadaniu' relacji rodzicielskich. W
każdym razie pomysł rodziecielstwa obiektów jest częsty, rówież w
C++/Delphi.
2. Inna idea Spring JDBC odnośnie często źle zwalnianych zasobów
(bazodanowych), zamyka je w przedział czasu. Wołasz funkcję (może trwać
nawet cięzkie minuty, w tym czasie kod apliacyjny wykonywany jest
callbackami) ale jak sterowanie wróci do ciebie wiesz, że jest posprzątane.
3. Trzecia idea, też z baz. Pule. Wywołanie 'new' jest jakby tam
zamknięte, uzywasz metody. Pule przeważnie chętnie by widziały close()
ale jak nie, to "radzą" sobie (opcjonalnie) na zasadzie
czasowo/testowej. Pochodne idee występują w twoim opowiadaniu.
To na razie tyle pomysłów, szkoda że nie orygilanych
-
3. Data: 2009-07-27 09:40:21
Temat: Re: Opowiadanie o GC
Od: Michal Kleczek <k...@g...com>
Maciej Sobczak wrote:
[ciach]
>
> W C++ typ Item ma destruktor, który wyrejestrowuje swoje id z mapy.
> Dzięki temu, jeśli program "porzuci" jakiś obiekt Item (przez co nie
> może już go obserwować i reagować na zmiany jego stanu), to
> odpowiadający mu wpis w mapie jest natychmiast wyrzucany.
Moglbys pokazac kod w C++ ktory to robi bez uzycia jakichs smart pointerow
zliczajacych referencje? Bo ja nie bardzo to widze (ale specem nie
jestem) - chyba ze wszystkie obiekty Item o tym samym id sa rownowazne -
tyle ze wtedy nie jest potrzebna zadna mapa.
> W
> szczególności, jeśli program używa obiektów Item w takiej pętli:
>
> for (...)
> {
> Item it;
> // ...
> }
>
> to w efekcie mapa nigdy nie ma więcej, niż 1 wpis. Zużycie pamięci
> jest wtedy *stałe* (albo ma znaną górną granicę) - dotyczy to również
> bardziej złożonych, ale nadal cyklicznych układów.
Nie widze w powyzszym przykladzie mapy ani pobierania obiektow Item z tejze.
>
> W Javie nie ma destruktorów, ale jest GC. Jeśli program "porzuci"
> jakiś obiekt Item, to w pamięci wisi zarówno obiekt Item, jak i
> odpowiadający jego id wpis w mapie. Wyciek.
> Na szczęście są słabe referencje, ale uwaga - Item jest *wartością* a
> nie kluczem w mapie. W najlepszym razie GC posprząta obiekty Item, ale
> wpisy w mapie pozostaną, przez co mapa i tak puchnie w nieskończoność.
> Wyciek.
> Można pomyśleć o podpięciu się pod finalizator, ale w opinii
> szanowanych obywateli jest to robienie wiochy. Zamiast tego doradzono
> mi zrobić *dodatkowy wątek*, który będzie okresowo jeździł po mapie i
> wywalał wpisy, których wartości się wyzerowały (czyli te wpisy,
> których obiekty Item zostały posprzątane przez GC).
> To rozwiązanie *prawie* działa, ale ma takie dwie przykre cechy:
> * Komplikuje projekt wymuszając istnienie dodatkowego wątku.
Prawda.
> * Wprowadza dodatkowe wartości konfiguracyjne służące do strojenia
> pracy tego wątku (okres skanowania).
>
Nie trzeba watku skanujacego - wystarczy usuwajacy. Od skanowania jest
java.lang.ref.ReferenceQueue
--
Michal
-
4. Data: 2009-07-27 09:50:54
Temat: Re: Opowiadanie o GC
Od: Krzysiek Kowaliczek <k...@g...com>
Maciej Sobczak wrote:
[ciach]
> Piszę *prawie*, bo pomiędzy posprzątaniem obiektów Item (hint: to się
> dzieje dopiero wtedy, gdy GC jest wystarczająco zestresowany, żeby się
> w ogóle ruszyć - co następuje akurat wtedy, gdy *zaczyna brakować
> pamięci*) a skanem mapy jest pewien odstęp czasu, kiedy niepotrzebne
> już wpisy w mapie *nadal* zajmują pamięć, zmniejszając efektywnie pulę
> dostępnej pamięci. To powoduje, że przy dalszej pracy programu GC
> szybciej będzie zestresowany i znowu coś tam posprząta, ale przez nowe
> wiszące wpisy w mapie i tak zostawi jeszcze mniejszą pulę wolnej
> pamięci. Ostatecznie program wylatuje na jej braku, chociaż z
> projektowego punktu widzenia może nigdy nie używać więcej, niż jednego
> obiektu Item.
> Oczywiście zmiany okresu skanowania mapy wpływają jedynie na
> *prawdopodobieństwo* poprawnego działania całego programu i nigdy nie
> można tej poprawności *zagwarantować*.
>
Dlaczego? Watek sprzątający może sam "kopnąć" GC.
> Ot, taka sobie historyjka. Ale może ktoś znudzony upałami i burzami
> wpadnie na jakiś pomysł, jak to poprawić.
>
> Jedną z możliwości jest dodatnie do klasy Item funkcji close() i
> uprzejme poproszenie programisty, żeby jej używał. Jest to
> rozwiązanie, którego poziom abstrakcji i wartość projektowa
> odpowiadają językowi C.
To nie jest złe. Dla bezpieczeństwa w finalizatorze można
dodać asercje o niezwolnionym obiekcie.
> Są inne?
>
Może zobaczyć jak zaimplementowali WeakHashMap ( tam "weak"
jest klucz a nie wartość ).
Pozdrawiam
KK
-
5. Data: 2009-07-27 10:09:38
Temat: Re: Opowiadanie o GC
Od: Michal Kleczek <k...@g...com>
Michal Kleczek wrote:
[ciach]
>
> Nie trzeba watku skanujacego - wystarczy usuwajacy. Od skanowania jest
> java.lang.ref.ReferenceQueue
>
Sprobuj moze jakos tak:
class IdMap<ID, ITEM> {
private final Map<Reference<?>, ID> refToIdMap = new HashMap();
private final Map<ID, Reference<ITEM>> idToRefMap = new HashMap();
private final ReferenceQueue<ITEM> referenceQueue = new ReferenceQueue();
//needs to be static so that we do not keep
//reference to the parent IdMap
private static class Remover<S, T> implements Runnable {
//keep just a (weak) reference to parent map
//so that we know when to finish removal
private final Reference<IdMap<S, T>> idMapRef;
private ReferenceQueue<T> getRefQueue() {
IdMap<S, T> idMap = idMapRef.get();
return idMap != null ? idMap.referenceQueue : null;
}
public Remover(IdMap<S, T> idMap) {
this.idMapRef = new WeakReference<IdMap<S, T>>(idMap);
}
@Override
public void run() {
try {
ReferenceQueue<T> referenceQueue = getRefQueue();
while (referenceQueue != null) {
//wait for next reference to remove
final Reference<? extends T> itemRef = referenceQueue.remove();
final IdMap<S, T> idMap = idMapRef.get();
if (idMap != null) {
idMap.remove(itemRef);
}
referenceQueue = getRefQueue();
}
}
catch (InterruptedException e) {
//ignore and return
}
}
}
private synchronized void remove(Reference<?> ref) {
final ID id = refToIdMap.remove(ref);
idToRefMap.remove(id);
}
public IdMap() {
new Thread(new Remover(this)).start();
}
public ITEM get(ID id) {
final Reference<ITEM> itemRef = idToRefMap.get(id);
return itemRef != null ? itemRef.get() : null;
}
public synchronized void put(ID id, ITEM item) {
final Reference<ITEM> itemRef = new WeakReference<ITEM>(item);
idToRefMap.put(id, itemRef);
refToIdMap.put(itemRef, id);
}
}
--
Michal
-
6. Data: 2009-07-27 10:11:52
Temat: Re: Opowiadanie o GC
Od: Krzysiek Kowaliczek <k...@g...com>
Michal Kleczek wrote:
> Maciej Sobczak wrote:
>
> [ciach]
>> W C++ typ Item ma destruktor, który wyrejestrowuje swoje id z mapy.
>> Dzięki temu, jeśli program "porzuci" jakiś obiekt Item (przez co nie
>> może już go obserwować i reagować na zmiany jego stanu), to
>> odpowiadający mu wpis w mapie jest natychmiast wyrzucany.
>
> Moglbys pokazac kod w C++ ktory to robi bez uzycia jakichs smart pointerow
> zliczajacych referencje? Bo ja nie bardzo to widze (ale specem nie
> jestem) - chyba ze wszystkie obiekty Item o tym samym id sa rownowazne -
> tyle ze wtedy nie jest potrzebna zadna mapa.
>
np.:
struct Item
{
~Item ()
{
mapa::releaseId ( id_ );
}
Item ()
{
id_ = mapa::getId ();
}
int id_;
};
for (...)
{
Item item;
}
>> W
>> szczególności, jeśli program używa obiektów Item w takiej pętli:
>>
>> for (...)
>> {
>> Item it;
>> // ...
>> }
>>
>> to w efekcie mapa nigdy nie ma więcej, niż 1 wpis. Zużycie pamięci
>> jest wtedy *stałe* (albo ma znaną górną granicę) - dotyczy to również
>> bardziej złożonych, ale nadal cyklicznych układów.
>
> Nie widze w powyzszym przykladzie mapy ani pobierania obiektow Item z tejze.
>
j.w.
Pozdrawiam
KK
-
7. Data: 2009-07-27 10:16:10
Temat: Re: Opowiadanie o GC
Od: Michal Kleczek <k...@g...com>
Krzysiek Kowaliczek wrote:
[ciach]
>
> Może zobaczyć jak zaimplementowali WeakHashMap ( tam "weak"
> jest klucz a nie wartość ).
>
O ile pamietam z WeakHashMap jest taki problem (jezeli to jest problem), ze
czyszczenie z obiektow WeakReference nastepuje dopiero w momencie uzycia
mapy.
--
Michal
-
8. Data: 2009-07-27 10:19:13
Temat: Re: Opowiadanie o GC
Od: Michal Kleczek <k...@g...com>
Krzysiek Kowaliczek wrote:
[ciach]
>
> np.:
> struct Item
> {
> ~Item ()
> {
> mapa::releaseId ( id_ );
> }
>
> Item ()
> {
> id_ = mapa::getId ();
> }
>
> int id_;
>
> };
>
> for (...)
> {
> Item item;
> }
>
Ja chyba potrzebuje jasniej :)
Nadal nie widze tutaj pobierania obiektow Item z mapy.
Co robi mapa::getId() ?
Michal
--
Michal
-
9. Data: 2009-07-27 10:21:31
Temat: Re: Opowiadanie o GC
Od: Michal Kleczek <k...@g...com>
Michal Kleczek wrote:
[ciach]
> public synchronized void put(ID id, ITEM item) {
> final Reference<ITEM> itemRef = new WeakReference<ITEM>(item);
tu oczywiscie new WeakReference(item, referenceQueue);
--
Michal
-
10. Data: 2009-07-27 10:24:49
Temat: Re: Opowiadanie o GC
Od: "Sebastian Nibisz" <e...@p...onet.pl>
Ja zaproponuje takie rozwiązanie.
1. Oprócz mapy kluczy, utworzyć kolejkę par [ID, Item].
2. W konstruktorze obiektu Item
a) pobrać N > 1 par z kolejki,
b) usunąć z mapy wpisy z martwymi referencjami,
c) pary z żywymi referencjami dodać na koniec kolejki,
d) utworzyć parę [ID, Item] dla bieżącego obiektu i dodać ja do mapy,
oraz na koniec kolejki.
Dla przyjętej wartości N można wyliczyć, procentową zajętość
niewykorzystanej pamięci, dla pesymistycznego przypadku.
Pozdrawiam,
- Bastek -