Czy powinniśmy unikać tworzenia obiektów w Javie?

242

Kolega powiedział mi, że tworzenie obiektów Java jest najdroższą operacją, jaką można wykonać. Mogę więc tylko stwierdzić, że mogę utworzyć jak najmniej obiektów.

Wydaje się to nieco pokonywać cel programowania obiektowego. Jeśli nie tworzymy obiektów, piszemy tylko jeden długi styl klasy C w celu optymalizacji?

Slamice
źródło
39
„Tworzenie obiektów Java jest najdroższą operacją, jaką możesz wykonać” . Chcesz podzielić się źródłem tego roszczenia?
Songo
92
I - najdroższa operacja w porównaniu do czego? W porównaniu do obliczania liczby pi do 900 cyfr lub w porównaniu do zwiększania liczby całkowitej?
jasonk
4
Czy mogli rozmawiać o konkretnej części stworzenia tego konkretnego obiektu? Zastanawiam się, jak Apple używa kolejki dla tableViewCells. Być może twój kolega sugerował, aby utworzyć kilka obiektów i użyć ich ponownie z powodu pewnych kosztów ogólnych związanych z tymi konkretnymi obiektami? Próbuję tylko dowiedzieć się, dlaczego tak twierdzą.
Tyler DeWitt
3
To jedna z najśmieszniejszych rzeczy, jakie słyszałem o programowaniu.)
Mert Akcakaya
22
Arytmatyka jest również dość droga; powinniśmy unikać obliczeń, och, a uruchomienie JVM jest naprawdę drogie, więc nie powinniśmy uruchamiać żadnych JVM :-).
James Anderson

Odpowiedzi:

478

Twój kolega nie ma pojęcia o czym mówi.

Twoja najdroższa operacja będzie ich słuchać . Zmarnowali czas na przekręcanie cię na informacje, które są ponad dziesięć lat nieaktualne (od pierwotnej daty opublikowano tę odpowiedź), a także na poświęcanie czasu na pisanie tutaj i poszukiwanie prawdy w Internecie.

Miejmy nadzieję, że po prostu ignorują coś, co słyszeli lub czytali ponad dziesięć lat temu i nie wiedzą nic lepszego. Przyjęłbym również wszystko, co powiedzą, jako podejrzane, powinien to być dobrze znany błąd każdego, kto i tak jest na bieżąco.

Wszystko jest przedmiotem (oprócz primitives)

Wszystko inne niż prymitywy ( int, long, doubleitp.) To Obiekty w Javie. Nie ma możliwości uniknięcia tworzenia obiektów w Javie.

Tworzenie obiektów w Javie ze względu na strategie alokacji pamięci jest w większości przypadków szybsze niż C ++ i ze względów praktycznych w porównaniu do wszystkiego innego w JVM można uznać za „wolne” .

Na początku lat dziewięćdziesiątych na początku lat 2000. implementacje JVM miały pewne narzuty wydajności w rzeczywistej alokacji obiektów. Nie było tak od co najmniej 2005 roku.

Jeśli dostroisz się, -Xmsaby obsługiwać całą pamięć potrzebną do poprawnego działania aplikacji, GC może nigdy nie być zmuszona do uruchomienia i zamiatania większości śmieci w nowoczesnych implementacjach GC, programy krótkotrwałe mogą nigdy nie być GC.

Nie próbuje zmaksymalizować wolnego miejsca, które i tak jest czerwonym śledziem, maksymalizuje wydajność środowiska wykonawczego. Jeśli to oznacza, że ​​JVM Heap jest przydzielany prawie w 100% przez cały czas, niech tak będzie. Wolna pamięć sterty JVM i tak niczego nie daje.

Istnieje błędne przekonanie, że GC zwalnia pamięć z powrotem do reszty systemu w użyteczny sposób, jest to całkowicie nieprawda!

Sterta JVM nie rośnie i nie kurczy się, więc na resztę systemu pozytywnie wpływa wolna pamięć w stercie JVM . -Xmsalokuje WSZYSTKO, co jest określone podczas uruchamiania, a jego heurystyka polega na tym, że tak naprawdę nigdy nie zwalnia żadnej pamięci z powrotem do systemu operacyjnego, aby mogła być współdzielona z innymi procesami systemu operacyjnego, dopóki ta instancja JVM nie zakończy się całkowicie. -Xms=1GB -Xmx=1GBprzydziela 1 GB pamięci RAM, niezależnie od liczby obiektów faktycznie utworzonych w danym momencie. Istnieją pewne ustawienia, które pozwalają na zwolnienie procentu pamięci sterty, ale dla wszystkich praktycznych celów JVM nigdy nie jest w stanie zwolnić wystarczającej ilości tej pamięci, aby to się kiedykolwiek wydarzyłowięc żadne inne procesy nie mogą odzyskać tej pamięci, więc reszta systemu nie korzysta z uwolnienia JVM Heap. Zapytanie RFE zostało „przyjęte” 29 listopada 2006 r., Ale nic z tym nie zrobiono. Takie zachowanie nie jest uważane za problem przez nikogo z autorytetów.

Istnieje błędne przekonanie, że tworzenie wielu małych, krótkotrwałych obiektów powoduje, że JVM zatrzymuje się na długi okres czasu, jest to również nieprawda

Obecne algorytmy GC są w rzeczywistości zoptymalizowane do tworzenia wielu wielu małych obiektów, które są krótkotrwałe, to jest w zasadzie 99% heurystyka dla obiektów Java w każdym programie. Próby łączenia obiektów powodują, że JVM działa gorzej w większości przypadków.

Jedynymi obiektami, które wymagają dziś pulowania, są obiekty odnoszące się do skończonych zasobów, które są zewnętrzne w stosunku do JVM; Gniazda, pliki, połączenia z bazą danych itp. Mogą być ponownie użyte. Zwykłe przedmioty nie mogą być łączone w taki sam sposób, jak w językach, które pozwalają na bezpośredni dostęp do miejsc pamięci. Buforowanie obiektów jest inną koncepcją i może być, lub nie, to, co niektórzy naiwnie nazywają pulowaniem , te dwie koncepcje nie są tym samym i nie należy ich łączyć.

Współczesne algorytmy GC nie mają tego problemu, ponieważ nie zwalniają zgodnie z harmonogramem, zwalniają, gdy w danym pokoleniu potrzebna jest wolna pamięć. Jeśli sterta jest wystarczająco duża, wówczas nie następuje dezalokacja na tyle długo, aby spowodować przerwy.

Języki dynamiczne zorientowane obiektowo pokonują C nawet teraz w testach wrażliwych na obliczenia.

Społeczność
źródło
274
+1: „Najdroższą operacją byłoby słuchanie ich ...”. Najlepiej słyszałem od jakiegoś czasu.
rjnilsson
21
@DeadMG, nawet przy skumulowanym obciążeniu GC, Java może być szybsza niż C ++ (np. Ze względu na zagęszczenie sterty, minimalizując straty w pamięci podręcznej dla niektórych struktur danych).
SK-logic
13
@ SK-logic: Ponieważ GC jest niedeterministyczna, niezwykle trudno jest to udowodnić. Jeśli chodzi o minimalizowanie braków w pamięci podręcznej, trudno to zrobić, gdy każdy obiekt musi być inną pośrednią funkcją, marnując pamięć podręczną i wydłużając czas wykonywania. Jednak twierdzę, że po prostu używając odpowiedniego alokatora w C ++, takiego jak pula obiektów lub arena pamięci, możesz łatwo dopasować lub pobić wydajność kolektora śmieci. Na przykład możesz napisać klasę areny pamięci, która będzie działać szybciej niż _allocazamortyzowana.
DeadMG
33
Tworzenie obiektów jest teraz tańsze niż kiedyś. Nie jest jednak darmowy. Każdy, kto mówi ci, że kłamie. Link do języków OO pokonujących C jest przesadzoną odpowiedzią dla kogoś, kto naprawdę próbuje się uczyć.
jasonk
17
To z powodu takich odpowiedzi powstaje gówniany kod. Prawidłowa odpowiedź to utworzenie obiektu w Javie, zarówno utworzenie obiektu Java, jak i jego zainicjowanie. Pierwsza część jest tania, druga może być bardzo droga. Ludzie powinni zawsze patrzeć na to, co dzieje się w konstruktorach, zanim użyje się newsłowa kluczowego w hotspocie. Widziałem ludzi korzysta new ImageIcon(Image)w paint()sposobie obiektów huśtawka, która jest dość drogie i robił bardzo słaby cały UI. Więc to nie jest czarno-biała odpowiedź, zastanów się, zanim newgdzieś skorzystasz .
qwertzguy
94

Konkluzja: Nie narażaj projektu na działanie, aby tworzyć skróty przy tworzeniu obiektów. Unikaj niepotrzebnego tworzenia obiektów. Jeśli jest to rozsądne, należy zaprojektować, aby uniknąć zbędnych operacji (jakiegokolwiek rodzaju).

W przeciwieństwie do większości odpowiedzi - tak, przydział obiektów NIE wiąże się z kosztami. Jest to niski koszt, ale należy unikać tworzenia niepotrzebnych przedmiotów. Tak samo, jak powinieneś unikać niepotrzebnych elementów w kodzie. Duże wykresy obiektowe spowalniają GC, implikują dłuższe czasy wykonywania, ponieważ prawdopodobnie będziesz mieć więcej wywołań metod, wyzwala więcej braków pamięci podręcznej procesora i zwiększa prawdopodobieństwo zamiany twojego procesu na dysk w przypadkach małej pamięci RAM.

Zanim ktokolwiek narzeka na to, że jest to przypadek skrajny - mam profilowane aplikacje, które przed optymalizacją utworzyły 20 + MB obiektów w celu przetworzenia ~ 50 wierszy danych. Testowanie jest w porządku, dopóki nie skalujesz do stu żądań na minutę i nie tworzysz nagle 2 GB danych na minutę. Jeśli chcesz zrobić 20 reqs / s, tworzysz 400 MB obiektów, a następnie wyrzucasz. 20 reqs / s to niewielka ilość jak na porządny serwer.

Jasonk
źródło
7
Mogę dodać przykład: w niektórych przypadkach tak naprawdę nie robi różnicy pod względem przejrzystości kodu, ale może mieć mniejszą lub większą różnicę w wydajności: Na przykład podczas czytania ze strumieni nierzadko używa się czegoś takiego, while(something) { byte[] buffer = new byte[10240]; ... readIntoBuffer(buffer); ...co może być marnotrawnym w porównaniu do np byte[] buffer = new byte[10240]; while(something) { ... readIntoBuffer(buffer); ....
JimmyB
4
+1: Tworzenie niepotrzebnych obiektów (a raczej sprzątanie po usunięciu) może czasami być kosztowne. Grafiki 3D / kod OpenGL w Javie to jedno z miejsc, w których widziałem optymalizacje mające na celu zminimalizowanie liczby tworzonych obiektów, ponieważ GC może siać spustoszenie w twoim framerate.
Lew
1
Łał! Ponad 20 MB do przetworzenia 50 wierszy danych? Brzmi szalenie. W każdym razie, czy te obiekty są długowieczne? Ponieważ tak jest w przypadku GC. Z drugiej strony, jeśli omawiasz jedynie wymagania dotyczące pamięci , nie ma to związku z wydajnością wyrzucania elementów bezużytecznych lub tworzenia obiektów ...
Andres F.,
1
Obiekty są krótkotrwałe (mniej niż 0,5 sekundy w ogólnym przypadku). Ta ilość śmieci nadal wpływa na wydajność.
jasonk
2
Dziękujemy za to, że jesteście wierni rzeczywistości, każdy, kto żyje z efektem bezmyślnej architektury, może bardzo się odnieść.
JM Becker,
60

W rzeczywistości, ze względu na strategie zarządzania pamięcią, które umożliwia język Java (lub inny język zarządzany), tworzenie obiektów to niewiele więcej niż zwiększanie wskaźnika w bloku pamięci zwanym młodym pokoleniem. Jest znacznie szybszy niż C, gdzie trzeba wykonać wyszukiwanie wolnej pamięci.

Inną częścią kosztu jest zniszczenie obiektu, ale trudno go porównać do C. Koszt kolekcji opiera się na ilości obiektów zapisanych długoterminowo, ale częstotliwość kolekcji zależy od ilości obiektów utworzonych ... w koniec jest wciąż znacznie szybszy niż zarządzanie pamięcią w stylu C.

amara
źródło
7
+1 Mimo że śmieciarz „po prostu działa”, każdy programista Java powinien dowiedzieć się o generacyjnym śmieciarzu.
benzado
18
Nowsze wersje Javy mogą przeprowadzać analizę zmian znaczenia, co oznacza, że ​​może przydzielić pamięć dla obiektów, które nie unikają metody na stosie, dzięki czemu ich czyszczenie jest bezpłatne - śmieciarz nie musi zajmować się tymi obiektami , są automatycznie odrzucane, gdy ramka stosu metody jest rozwijana (gdy metoda powraca).
Jesper
4
AN następny etap analizy ewakuacji jest „przydzielenie” pamięć małego przedmiotu wyłącznie w rejestrach (na przykład Pointobiektu może się zmieścić w 2 rejestrów ogólnego przeznaczenia).
Joachim Sauer
6
@Jachach Sauer: Tak właśnie dzieje się w nowszych implementacjach maszyny wirtualnej HotSpot. Nazywa się to zastępowaniem skalarnym.
pewnego
1
@someguy: Przeczytałem o tym jakiś czas temu jako następną rzecz, ale nie sprawdziłem, czy to już zostało zrobione. To świetna wiadomość, że już to mamy.
Joachim Sauer
38

Inne plakaty słusznie zauważyły, że tworzenie obiektów jest niezwykle szybkie w Javie i że zwykle nie powinieneś się tym przejmować w żadnej normalnej aplikacji Java.

Istnieje kilka bardzo szczególnych sytuacjach, gdy jest to dobry pomysł, aby uniknąć tworzenia obiektów.

  • Kiedy piszesz aplikację wrażliwą na opóźnienia i chcesz uniknąć pauz GC. Im więcej przedmiotów wyprodukujesz, tym więcej GC się wydarzy i tym większa szansa na przerwy. Może to być ważne w przypadku gier, niektórych aplikacji multimedialnych, sterowania robotami, handlu o wysokiej częstotliwości itp. Rozwiązaniem jest wstępne przydzielenie wszystkich potrzebnych obiektów / tablic z góry i ponowne ich użycie. Istnieją biblioteki specjalizujące się w zapewnianiu tego rodzaju możliwości, np . Javolution . Ale prawdopodobnie, jeśli naprawdę zależy Ci na niskim opóźnieniu, powinieneś używać C / C ++ / asemblera zamiast Java lub C # :-)
  • Unikanie prymitywów pudełkowych (Double, Integer itp.) Może być bardzo korzystną mikrooptymalizacją w pewnych okolicznościach. Ponieważ prymitywy rozpakowane (podwójne, całkowite itp.) Unikają narzutu na obiekt, są one znacznie szybsze i intensywnie wykorzystują procesor, np. Przetwarzanie numeryczne itp. Zazwyczaj pierwotne tablice działają bardzo dobrze w Javie, więc chcesz ich używać do kruszenia liczb zamiast wszelkie inne przedmioty.
  • W sytuacjach z ograniczoną pamięcią chcesz zminimalizować liczbę (aktywnych) obiektów utworzonych, ponieważ każdy z nich ma niewielki narzut (zwykle 8-16 bajtów w zależności od implementacji JVM). W takich okolicznościach powinieneś preferować niewielką liczbę dużych obiektów lub tablic do przechowywania danych zamiast dużej liczby małych obiektów.
mikera
źródło
3
Warto zauważyć, że analiza ucieczki pozwoli na przechowywanie na stosie obiektów o krótkim życiu (ograniczonym życiu), obiekty te można zwolnić za darmo ibm.com/developerworks/java/library/j-jtp09275/index.html
Richard Tingle
1
@Richard: świetny punkt - analiza ucieczki zapewnia kilka bardzo dobrych optymalizacji. Trzeba jednak być ostrożnym: polegać na tym, że nie we wszystkich implementacjach Java i we wszystkich okolicznościach. Dlatego często musisz przeprowadzić test porównawczy, aby się upewnić.
mikera
2
Ponadto 100% poprawna analiza ucieczki jest równoważna problemowi zatrzymania. Nie polegaj na analizie ucieczki w skomplikowanych sytuacjach, ponieważ może to dać złą odpowiedź przynajmniej przez pewien czas.
Jules
Pierwszy i ostatni punkt nie dotyczą małych obiektów, które są szybko uwalniane, giną w Edenie (pomyśl o przydzieleniu stosu w C, prawie za darmo) i nigdy nie wpływają na czasy GC ani na przydzielanie pamięci. Większość przydziałów obiektów w Javie należy do tej kategorii i dlatego jest zasadniczo bezpłatna. Drugi punkt jest nadal aktualny.
Bill K
17

W tym, co mówi twój kolega, jest jądro prawdy. Szacunkiem sugerować problem z obiektowego tworzenia jest rzeczywiście śmieci kolekcja . W C ++ programista może dokładnie kontrolować sposób zwalniania pamięci. Program może gromadzić rudę tak długo lub tak krótko, jak chce. Co więcej, program C ++ może odrzucić crud, używając innego wątku niż ten, który go stworzył. W ten sposób działający obecnie wątek nigdy nie musi kończyć się w celu oczyszczenia.

Natomiast wirtualna maszyna Java (JVM) okresowo zatrzymuje kod w celu odzyskania nieużywanej pamięci. Większość programistów Java nigdy nie zauważa tej przerwy, ponieważ zwykle jest ona rzadka i bardzo krótka. Im więcej zgrubienia gromadzisz lub im bardziej ograniczona jest JVM, tym częstsze są te przerwy. Do wizualizacji tego procesu można użyć narzędzi takich jak VisualVM .

W najnowszych wersjach Javy algorytm odśmiecania (GC) można dostosować . Zasadą jest, że im krótsze są przerwy, tym droższy jest koszt maszyny wirtualnej (tj. Procesor i pamięć spędzone na koordynowaniu procesu GC).

Kiedy to może mieć znaczenie? Za każdym razem, gdy zależy Ci na spójnym wskaźniku odpowiedzi poniżej milisekund, zależy Ci na GC. Zautomatyzowane systemy transakcyjne napisane w Javie dostrajają JVM, aby zminimalizować przerwy. Firmy, które w innym przypadku napisałyby Javę, zwracają się do C ++ w sytuacjach, gdy systemy muszą być bardzo responsywne przez cały czas.

Dla przypomnienia, ogólnie nie toleruję unikania obiektów! Domyślnie programowanie obiektowe. Dostosuj to podejście tylko wtedy, gdy GC stanie ci na przeszkodzie, i dopiero po próbie dostrojenia JVM, aby zatrzymać na krótszy czas. Dobrą książką na temat dostrajania wydajności Java jest Java Performance autorstwa Charliego Hunta i Binu Johna.

greg
źródło
10
Większość współczesnych JVM obsługuje lepsze algorytmy wyrzucania elementów bezużytecznych niż „zatrzymanie świata”. Zamortyzowany czas spędzony na zbieraniu śmieci jest często krótszy niż ten, który jest jawnie nazywany malloc / free. Czy zarządzanie pamięcią C ++ działa również w osobnym wątku?
10
Nowsze wersje Java firmy Oracle mogą przeprowadzać analizę zmian znaczenia, co oznacza, że ​​obiekty, które nie uciekają przed metodą, są przydzielane na stosie, dzięki czemu ich czyszczenie jest bezpłatne - są automatycznie zwalniane po powrocie metody.
Jesper
2
@ ThorbjørnRavnAndersen: Naprawdę? Czy naprawdę masz na myśli GC bez pauzy , czy masz na myśli GC „zwykle równolegle-ale-czasami-trochę-pauzujący”? Nie rozumiem, w jaki sposób GC nigdy nie może wstrzymać programu, a jednocześnie przesuwać obiekty w tym samym czasie ... wydaje mi się, dosłownie, niemożliwe ...
Mehrdad
2
@ ThorbjørnRavnAndersen: Jak to może „zatrzymać świat”, ale nigdy nie „zatrzymać JVM”? To nie ma sensu ...
Mehrdad
2
@ ThorbjørnRavnAndersen: Nie, naprawdę nie; Po prostu nie rozumiem co mówisz. Albo GC czasami wstrzymuje program, w którym to przypadku - z definicji - nie jest „ pauza ”, albo nigdy nie wstrzymuje programu (stąd jest „pauza”), w którym to przypadku nie rozumiem, jak to odpowiada twojemu stwierdzeniu „zatrzymuje świat, gdy nie może nadążyć”, ponieważ wydaje się to sugerować, że program jest zatrzymany podczas działania GC. Czy mógłbyś wyjaśnić, co się dzieje (czy jest to pauza czy nie?) I jak to możliwe?
Mehrdad
11

Jest jeden przypadek, w którym możesz zniechęcić się do tworzenia zbyt wielu obiektów w Javie z powodu narzutu - projektowania pod kątem wydajności na platformie Android

Poza tym powyższe odpowiedzi są prawdziwe.

Peter Kelly
źródło
2
Przeczytaj pytanie jeszcze raz. Nigdzie nie określa JVM, w tym znaczników. Wspomina tylko o Javie.
Peter Kelly
9

GC jest dostrojony do wielu krótkotrwałych obiektów

powiedział, że jeśli możesz w trywialny sposób ograniczyć przydział obiektów, powinieneś

jednym z przykładów byłoby budowanie łańcucha w pętli, naiwnym sposobem byłoby

String str = "";
while(someCondition){
    //...
    str+= appendingString;
}

który tworzy nowy Stringobiekt dla każdej +=operacji (plus a StringBuilderi nową podstawową tablicę char)

możesz łatwo przepisać to na:

StringBuilder strB = new StringBuilder();
while(someCondition){
    //...
    strB.append(appendingString);
}
String str = strB.toString();

ten wzorzec (niezmienny wynik i lokalna zmienna wartość pośrednia) można również zastosować do innych rzeczy

ale poza tym powinieneś wyciągnąć profiler, aby znaleźć prawdziwe wąskie gardło zamiast ścigać duchy

maniak zapadkowy
źródło
Największą zaletą tej StringBuildermetody jest pre-sizeStringBuilder tak, że nigdy nie ma do realokacji bazowego tablicę z StringBuilder(int)konstruktora. To sprawia, że ​​jest to pojedynczy przydział, a nie 1+Nprzydział.
2
@JarrodRoberson StringBuilder co najmniej podwoi obecną pojemność lub innymi słowy, pojemność wzrośnie wykładniczo tylko dla przydziałów log (n)
maniak ratchet
6

Joshua Bloch (jeden z twórców platform Java) napisał w swojej książce Effective Java w 2001 roku:

Unikanie tworzenia obiektów przez utrzymywanie własnej puli obiektów jest złym pomysłem, chyba że obiekty w puli są wyjątkowo ciężkie. Prototypowym przykładem obiektu uzasadniającego pulę obiektów jest połączenie z bazą danych. Koszt nawiązania połączenia jest na tyle wysoki, że sensowne jest ponowne użycie tych obiektów. Ogólnie jednak utrzymanie własnych pul obiektów powoduje zaśmiecanie kodu, zwiększenie powierzchni pamięci i obniżenie wydajności. Nowoczesne implementacje JVM mają wysoce zoptymalizowane kolektory śmieci, które z łatwością przewyższają takie pule obiektów na lekkich obiektach.

Anthony Ananich
źródło
1
Nawet w przypadku połączeń sieciowych ważne jest nie utrzymanie obiektów przydzielonych do GC powiązanych z połączeniami, ale raczej utrzymanie zestawu połączeń, które istnieją poza światem zarządzanym przez GC. Są chwile, kiedy same obiekty mogą być pomocne; większość z nich wynika z przypadków, w których para odniesień do tego samego obiektu może być semantycznie równoważna parze odniesień do identycznych, ale odrębnych obiektów, ale ma jednak pewne zalety. Na przykład dwa odniesienia do tego samego ciągu pięćdziesięciu tysięcy znaków można sprawdzić pod kątem równości ...
supercat
2
... znacznie szybciej niż odniesienia do dwóch wyraźnych, ale identycznych ciągów o tej długości.
supercat
5

To zależy od konkretnej aplikacji, więc ogólnie trudno jest powiedzieć. Byłbym jednak bardzo zaskoczony, gdyby tworzenie obiektów było faktycznie wąskim gardłem wydajności w aplikacji. Nawet jeśli są powolne, korzyści w stylu kodu prawdopodobnie przewyższą wydajność (chyba że jest to zauważalne dla użytkownika)

W każdym razie nie powinieneś nawet martwić się o te rzeczy, dopóki nie wyprofilujesz swojego kodu, aby określić rzeczywiste wąskie gardła wydajności zamiast zgadywania. Do tego czasu powinieneś robić wszystko, co najlepsze dla czytelności kodu, a nie wydajności.

Oleksi
źródło
We wczesnych wersjach Javy koszta były bardzo kosztowne, gdy śmieciarz czyścił odrzucone obiekty. Najnowsze wersje Java znacznie poprawiły opcje GC, więc tworzenie obiektów rzadko stanowi problem. Zwykle systemy internetowe są ograniczone wywołaniami We / Wy do systemów zewnętrznych, chyba że obliczasz coś naprawdę złożonego na serwerze aplikacji w ramach ładowania strony.
greg
3

Myślę, że twój kolega musiał to powiedzieć z perspektywy tworzenia niepotrzebnych obiektów. Mam na myśli, że jeśli często tworzysz ten sam obiekt, lepiej udostępnić ten obiekt. Nawet w przypadkach, w których tworzenie obiektów jest skomplikowane i zajmuje więcej pamięci, możesz sklonować ten obiekt i uniknąć tworzenia procesu tworzenia złożonych obiektów (ale to zależy od twoich wymagań). Myślę, że stwierdzenie „Tworzenie obiektów jest drogie” powinno być brane pod uwagę w kontekście.

Jeśli chodzi o wymagania dotyczące pamięci JVM, poczekaj na Javę 8, nie musisz nawet określać -Xmx, ustawienia przestrzeni meta zajmą się potrzebą pamięci JVM i będzie rosła automatycznie.

AKS
źródło
Byłoby bardzo konstruktywne, gdyby ludzie komentowali również podczas głosowania nad odpowiedzią. W końcu wszyscy jesteśmy tutaj, aby dzielić się wiedzą, a zwykłe wydawanie osądów bez uzasadnionego powodu nie służy żadnemu celowi.
AKS
1
Wymiana stosów powinna mieć mechanizm powiadamiania użytkowników, którzy głosują na dowolną odpowiedź, gdy komentarz zostanie opublikowany na tej odpowiedzi.
AKS
1

Tworzenie klasy to coś więcej niż przydzielanie pamięci. Istnieje również inicjalizacja, nie jestem pewien, dlaczego ta część nie jest objęta wszystkimi odpowiedziami. Normalne klasy zawierają pewne zmienne i wykonują jakąś formę inicjalizacji, a to nie jest darmowe. W zależności od klasy klasa może odczytywać pliki lub wykonywać dowolną liczbę powolnych operacji.

Zastanów się więc, co robi konstruktor klasy, zanim zorientujesz się, czy jest darmowy, czy nie.

Alex
źródło
2
Dobrze zaprojektowana klasa nie powinna wykonywać ciężkich zadań w swoim konstruktorze z tego właśnie powodu. Na przykład klasa odczytu plików może obniżyć koszty tworzenia instancji, jedynie sprawdzając, czy plik docelowy istnieje podczas uruchamiania i odraczając wszelkie rzeczywiste operacje na plikach do czasu pierwszego wywołania metody wymagającego danych z pliku.
GordonM
2
Jeśli dodatkowy koszt zostanie przeniesiony do „if (firstTime)”, wówczas odtworzenie klasy w pętli jest nadal droższe niż ponowne użycie klasy.
Alex
Już miałem opublikować podobną odpowiedź i uważam, że to właściwa odpowiedź. Tak wielu ludzi jest zaślepionych przez te powiedzenia, że ​​tworzenie obiektów Java jest tanie, że nie zdają sobie sprawy, że często konstruktorzy lub dalsza inicjalizacja mogą być dalekie od tanich. Na przykład klasa ImageIcon w Swing, nawet jeśli przekażesz jej wstępnie załadowany obiekt Image, konstruktor jest dość drogi. I nie zgadzam się z @GordonM, wiele klas z JDK wykonuje większość inicjacji w konstruktorze, i myślę, że dzięki temu kod jest szczuplejszy, lepiej zaprojektowany.
qwertzguy
1

Java GC jest w rzeczywistości bardzo zoptymalizowana pod względem szybkiego tworzenia dużej liczby obiektów w trybie „seryjnym”. Z tego, co rozumiem, używają sekwencyjnego alokatora (najszybszego i najprostszego alokatora O (1) dla żądań o zmiennej wielkości) dla tego rodzaju „cyklu impulsów” w przestrzeni pamięci, którą nazywają „przestrzenią Edenu” i tylko wtedy, gdy obiekty pozostają po cyklu GC zostają przeniesieni do miejsca, w którym GC może je zbierać jeden po drugim.

To powiedziawszy, jeśli twoje potrzeby wydajnościowe stają się wystarczająco krytyczne (jak mierzone w rzeczywistych wymaganiach użytkowników), wtedy obiekty niosą narzut, ale nie pomyślałbym o tym tak dużo w zakresie tworzenia / alokacji. Ma to więcej wspólnego z lokalizacją odniesienia i dodatkowym rozmiarem wszystkich obiektów w Javie, niezbędnym do obsługi pojęć takich jak odbicie i dynamiczne wysyłanie ( Floatjest większy niż float, często około 4 razy większy w 64-bitowych wymaganiach wyrównania, oraz tablica Floatnie musi koniecznie być przechowywana w sposób ciągły z tego, co rozumiem).

Jedną z najbardziej przyciągających uwagę rzeczy, które kiedykolwiek widziałem, opracowaną w Javie, która sprawiła, że ​​uznałem, że jest to zawodnik o dużej wadze w mojej dziedzinie (VFX), to interaktywny, wielowątkowy standardowy moduł śledzący ścieżki (nie korzystający z buforowania napromieniania, BDPT, MLS ani niczego innego) na procesor zapewnia podgląd w czasie rzeczywistym, który dość szybko zbiegał się w obraz pozbawiony szumów. Pracowałem z profesjonalistami w C ++ poświęcając swoją karierę takim rzeczom z fantazyjnymi profilerami w ręku, którzy mieli z tym trudności.

Ale rozejrzałem się po kodzie źródłowym i chociaż używał on wielu obiektów w trywialny sposób, najbardziej krytyczne części znacznika ścieżki (BVH oraz trójkąty i materiały) bardzo wyraźnie i celowo unikały obiektów na rzecz dużych tablic typów pierwotnych ( głównie float[]i int[]), co spowodowało, że zużywał on znacznie mniej pamięci i gwarantował lokalizację przestrzenną, aby przejść z jednej floattablicy do drugiej. Nie sądzę, aby myślenie w ten sposób było zbyt spekulacyjne, jeśli autor użył polubionych typów pudełkowychFloattam jego wydajność byłaby dość duża. Ale mówimy o najbardziej absolutnie krytycznej części tego silnika i jestem całkiem pewien, biorąc pod uwagę, jak umiejętnie deweloper zoptymalizował go, że zmierzył to i zastosował tę optymalizację bardzo, bardzo rozsądnie, ponieważ z radością używał obiektów wszędzie indziej trywialny koszt jego imponującego znacznika ścieżki w czasie rzeczywistym.

Kolega powiedział mi, że tworzenie obiektów Java jest najdroższą operacją, jaką można wykonać. Mogę więc tylko stwierdzić, że mogę utworzyć jak najmniej obiektów.

Nawet w dziedzinie tak krytycznej pod względem wydajności, jak moja, nie będziesz pisać skutecznych produktów, jeśli ściskasz się w miejscach, które nie mają znaczenia. Twierdziłbym nawet, że najbardziej krytyczne dla wydajności pola mogą spowodować jeszcze większe zapotrzebowanie na produktywność, ponieważ potrzebujemy dodatkowego czasu, aby dostroić te hotspoty, które są naprawdę ważne, nie marnując czasu na rzeczy, które nie . Podobnie jak w przypadku przykładu śledzenia ścieżki z góry, autor umiejętnie i rozważnie zastosował takie optymalizacje tylko w miejscach, które naprawdę, naprawdę miały znaczenie, i prawdopodobnie z perspektywy czasu po pomiarze i nadal z radością korzystały z obiektów wszędzie.

Dragon Energy
źródło
1
Twój pierwszy akapit jest na dobrej drodze. W przestrzeniach eden i młodych stosuje się kolektory kopii, które mają około. 0 koszt za martwe przedmioty. Bardzo polecam tę prezentację . Na marginesie, zdajesz sobie sprawę, że to pytanie pochodzi z 2012 roku, prawda?
JimmyJames
@JimmyJames Nudzę się i lubię po prostu przeglądać pytania, w tym stare. Mam nadzieję, że ludziom nie przeszkadza moja nekromancja! :-D
Dragon Energy
@JimmyJames Czy już nie jest tak, że typy pudełkowe mają dodatkowe wymagania dotyczące pamięci i nie gwarantuje, że będą ciągłe w tablicy? I mają tendencję do myślenia obiekty są tanie jak barszcz w Javie, ale jeden przypadek, w którym mogą być stosunkowo drogie jest jak float[]wersetach Float[], gdzie przetwarza milion byłego kolejno może być stosunkowo dość szybciej niż sekundę.
Dragon Energy,
1
Kiedy zacząłem tutaj zamieszczać posty, zrobiłem to samo. Tylko nie zdziw się, gdy odpowiedzi na stare pytania zwykle nie przyciągają uwagi.
JimmyJames,
1
Jestem pewien, że typy pudełkowe powinny zajmować więcej miejsca niż prymitywy. Są potencjalnie optymalizacje wokół tego, ale ogólnie myślę, że jest tak, jak to opisujesz. Gil Tene (z powyższej prezentacji) ma kolejną rozmowę na temat propozycji dodania specjalnego rodzaju kolekcji, która zapewniłaby niektóre zalety struktur, ale nadal korzystałaby z obiektów. To ciekawy pomysł, nie jestem pewien statusu JSR. Koszt jest w dużej mierze spowodowany czasem, do którego nawiązujesz. Jeśli możesz uniknąć pozwalania swoim obiektom na „ucieczkę”, można nawet przypisać stos i nigdy nie dotykać stosu.
JimmyJames
0

Jak powiedzieli ludzie, tworzenie obiektów nie jest dużym kosztem w Javie (ale stawiam na większe niż większość prostych operacji, takich jak dodawanie itp.) I nie powinieneś tego zbytnio unikać.

To powiedziało, że wciąż jest to koszt, a czasami możesz próbować zeskrobać jak najwięcej obiektów. Ale dopiero po tym, jak profilowanie wykazało, że jest to problem.

Oto świetna prezentacja na ten temat: https://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf

rkj
źródło
0

Zrobiłem szybki mikrobenchmark na ten temat i podałem pełne źródła w github. Doszedłem do wniosku, że to, czy tworzenie obiektów jest drogie, czy nie, nie jest problemem, ale ciągłe tworzenie obiektów z myślą, że GC zajmie się Twoimi sprawami, sprawi, że Twoja aplikacja szybciej uruchomi proces GC. GC jest bardzo kosztownym procesem i najlepiej jest go unikać, gdy tylko jest to możliwe, i nie próbuj naciskać go, aby rozpocząć.

Archimedes Trajano
źródło
wydaje się, że nie oferuje to nic istotnego w porównaniu z punktami przedstawionymi i wyjaśnionymi w poprzednich 15 odpowiedziach. Nie jest to treść warta obalenia pytania, które zostało zadane ponad 2 lata temu i które otrzymało bardzo dokładną odpowiedź
komara