JAVA "uchun" bayonotni amalga oshirish axlatni yig'ishni oldini oladi

UPD 21.11.2017: the bug is fixed in JDK, see comment from Vicente Romero

Xulosa:

Iterable ilovasi uchun foydalanilganda for ifodasi bo'lsa, to'plam joriy joyning oxirigacha (usul, ifodani tanasi)

http://bugs.java.com/bugdatabase/view_bug.do?bug_id = JDK-8175883

https://bugs.openjdk.java.net/browse/JDK-8175883

The example:

Agar tasodifiy kontent bilan katta satrlar ro'yxatini ajratadigan keyingi kodim bo'lsa:

import java.util.ArrayList;
public class IteratorAndGc {

   //number of strings and the size of every string
    static final int N = 7500;

    public static void main(String[] args) {
        System.gc();

        gcInMethod();

        System.gc();
        showMemoryUsage("GC after the method body");

        ArrayList strings2 = generateLargeStringsArray(N);
        showMemoryUsage("Third allocation outside the method is always successful");
    }

   //main testable method
    public static void gcInMethod() {

        showMemoryUsage("Before first memory allocating");
        ArrayList strings = generateLargeStringsArray(N);
        showMemoryUsage("After first memory allocation");


       //this is only one difference - after the iterator created, memory won't be collected till end of this function
        for (String string : strings);
        showMemoryUsage("After iteration");

        strings = null;//discard the reference to the array

       //one says this doesn't guarantee garbage collection,
       //Oracle says "the Java Virtual Machine has made a best effort to reclaim space from all discarded objects".
       //but no matter - the program behavior remains the same with or without this line. You may skip it and test.
        System.gc();

        showMemoryUsage("After force GC in the method body");

        try {
            System.out.println("Try to allocate memory in the method body again:");
            ArrayList strings2 = generateLargeStringsArray(N);
            showMemoryUsage("After secondary memory allocation");
        } catch (OutOfMemoryError e) {
            showMemoryUsage("!!!! Out of memory error !!!!");
            System.out.println();
        }
    }

   //function to allocate and return a reference to a lot of memory
    private static ArrayList generateLargeStringsArray(int N) {
        ArrayList strings = new ArrayList<>(N);
        for (int i = 0; i < N; i++) {
            StringBuilder sb = new StringBuilder(N);
            for (int j = 0; j < N; j++) {
                sb.append((char)Math.round(Math.random() * 0xFFFF));
            }
            strings.add(sb.toString());
        }

        return strings;
    }

   //helper method to display current memory status
    public static void showMemoryUsage(String action) {
        long free = Runtime.getRuntime().freeMemory();
        long total = Runtime.getRuntime().totalMemory();
        long max = Runtime.getRuntime().maxMemory();
        long used = total - free;
        System.out.printf("\t%40s: %10dk of max %10dk%n", action, used/1024, max/1024);
    }
}

kompilyatsiya va uni cheklangan xotira bilan ishlating (180mb):

javac IteratorAndGc.java   &&   java -Xms180m -Xmx180m IteratorAndGc

va men ish vaqtida:

Birinchi xotira ajratishdan oldin: 1251k max 176640k

     

Birinchi xotira ajratilganidan keyin: maksimal 176640k dan 131426k

     

Iteratsiyadan keyin: maksimal 176640k dan 131426k

     

Mashina jismida GC kuchidan so'ng: maksimum 176640k dan 110682k (deyarli hech narsa to'planmagan)

     

Oddiy usulda xotira ajratishga harakat qiling:

 !!!! Xotira xatosidan tashqarida !!!!: maksimum 176640k dan 168948k
 
-ni tanlang      

Metoddan keyin GC: 459k max 176640k (axlat to'planadi!)

     

Usuldan tashqari uchinchi ajratish har doim muvaffaqiyatli bo'ladi: maksimal 163840k dan 117740k

Masalan, gcInMethod() ichida ro'yxatni ajratish, uni yineleme, ro'yxatga havola qilish (ixtiyoriy) chiqindilarni yig'ish va o'xshash ro'yxati qayta taqsimlashga harakat qildim. Lekin xotira etishmasligi tufayli ikkinchi qatorni taqsimlay olmayman.

Shu bilan birga, funktsiya tanasi tashqarisida i chiqindilarni yig'ishni (ixtiyoriy) muvaffaqiyatli tarzda kuchaytirishi va bir xil o'lchamdagi hajmni yana ajratishi mumkin!

Funktsiya jismining ichida ushbu OutOfMemoryError oldini olish uchun faqat shu qatorni o'chirish/sharhlash kifoya:

for (String string : strings); <-- this is the evil!!!

va keyin chiqdi shunday ko'rinadi:

Birinchi xotira ajratishdan oldin: 1251k max 176640k

     

Birinchi xotira ajratgandan keyin: maksimal 176640k dan 131409k

     

Yinelemeden keyin: maksimal 176640k dan 131409k

     

Jihoz metodida GC kuchidan so'ng: 497k max 176640k (axlat to'planadi!)

     

Oddiy usulda xotira ajratishga harakat qiling:

     

Ikkinchi xotira ajratilganidan keyin: maksimal 163840k dan 115541k

     

Metoddan so'ng GC: maksimal 163840k dan 493k (axlat to'planadi!)

     

Usuldan tashqari uchinchi ajratish har doim muvaffaqiyatli bo'ladi: 121300k max 163840k

Shunday qilib, uchun bo'lmasdan, parchalanishning parchalanishini bekor qilib, ikkinchi marta (funktsional tananing ichida) ajratilgan va uchinchi marta (usuldan tashqarida) ajratilgan axlat qayta ishlangan.

Mening taxminlarim:

for syntax construction is compiled to

Iterator iter = strings.iterator();
while(iter.hasNext()){
    iter.next()
}

(va bu kodni qayta ishlashni tekshirdim javap -c IteratorAndGc.class )

Va shunga o'xshash iter mos yozuvi oxirigacha qamrab oladi. Siz uni bekor qilish uchun referentdan foydalana olmaysiz va GC to'plamni amalga oshira olmaydi.

Ehtimol, bu odatdagi xatti-harakatlar (ehtimol hatto javac da ko'rsatilishi mumkin, lekin men topmadim), lekin agar derleyici ba'zi hollarda ularni ishlatganidan keyin ularni o'chirishga ahamiyat berish kerak bo'lsa IMHO./strong>

for ifodasini amalga oshirishni kutmoqdaman:

Iterator iter = strings.iterator();
while(iter.hasNext()){
    iter.next()
}
iter = null;//<--- flush the water!

Java kompilyatori va ish vaqti versiyalari:

javac 1.8.0_111

java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)

Note:

  • savol dasturlash uslubi, eng yaxshi amaliyot, konventsiyalar va boshqalar, savol Java-ning samaradorligi haqida platforma.

  • savol System.gc() xatti-harakatiga bog'liq emas gc misollarni chaqiradi) - ikkinchi satrlarni ajratish paytida JVM zarflangan xotirani bo'shatish kerak

Reference to the test java class, Online compiler to test (but this resource has only 50 Mb of heap, so use N = 5000)

13
GC qanday ishlashini noto'g'ri tushunasiz. Bitta chaqiruvdan keyin GC har qanday narsani to'plashiga kafolat yo'q.
qo'shib qo'ydi muallif Andremoniy, manba
Nima bilan solishtirganda? To'plamni harakatga keltiradigan har qanday yo'li bilan unga havola qilish kerak. "Muammo" - bu sizning "ijro" bilan emas, balki kollektsiyani amalga oshirayotganingiz bilan bog'liq. NB "JVM ikkinchi jarringda ajratilgan xotirani bo'shatish kerak" noto'g'ri. Iltimos, jasur yuzni eching. Ko'zlarim og'riyapti.
qo'shib qo'ydi muallif EJP, manba
(a) Unga qanday murojaat qilsangiz, buni holda ga yuborishingiz mumkin? (b) JVM xotirani tashqariga ishlatganda faqat GC ishlatishi kerak.
qo'shib qo'ydi muallif EJP, manba
@avysk Siz derazaning JLSga mos kelmasligini yoki sizning savolingiz butunlay ma'nosiz ekanligini bildirasiz. Bu qaysi?
qo'shib qo'ydi muallif EJP, manba
Iterator kodini ishlatish uchun Iterator sinfini, va ilovasini ishlatilgan barcha bog'liqliklarni yuklaganini hisobga olmaysiz. Ular ham xotirani oladi. -XX: + TraceClassLoading VM argumenti bilan farqni tekshirish mumkin.
qo'shib qo'ydi muallif Jorn Vernee, manba
@radistao yep, bu go'zal. Kodni sinab ko'rdim - faqat uni boolean go = true deb o'zgartirish; if (go) {for (string string: strings); } muammoni echadi: D
qo'shib qo'ydi muallif avysk, manba
Agar siz bu "yaxshilangan" uchun unga mos keladigan narsani o'zgartirsangiz, biron bir farqni ko'rasizmi (qarang: docs.oracle.com/javase/specs/jls/se8/html/… )?
qo'shib qo'ydi muallif avysk, manba
Xafa, tavsifni o'qing, pls! GC chaqirig'i haqida emas! "savol System.gc() xatti-harakati bilan bog'liq emas (JCM ning jarohatlangan xotirani bo'shatish kerak bo'lganda ikkinchi satrlarni ajratish paytida). Savol "uchun" amalga oshirish haqida.
qo'shib qo'ydi muallif radistao, manba
@EJP va MEMORY OUT! Shuningdek, men derleyici "for" bayonotini qanday tuzganligini so'radim - u iterator-strukturaga yozildi.
qo'shib qo'ydi muallif radistao, manba
ni ishlatsam, u holda uchun (Iterator itr = strings.iterator (); itr.hasNext (); itr.next ()); da OutOfMemory- (iter.hasNext ()) {iter.next ()}; iter = null; - bu yaxshi ishlaydi, chunki men uni yineleyicikka aniq tashlayman.
qo'shib qo'ydi muallif radistao, manba
Agar men uni aylantirsam, nima uchun kolleksiyaga murojaat qilishim kerak? Nima uchun JVM dastur xotira talab qilsa, axlat yig'ishlarini o'tkazib yuborishi mumkin?
qo'shib qo'ydi muallif radistao, manba
baribir: -XX: + TraceClassLoading bilan sinab ko'rildi: barcha sinflar birinchi xotira ajratishdan oldin yuklanadi.
qo'shib qo'ydi muallif radistao, manba
Sizning fikringizcha, Iterator va tegishli sinf yuklanishi 150 Mb xotira olishi kerakmi? yo'q deb o'ylayman. Bundan tashqari, funktsiyani tark etsam, o'sha Iterator sinflari o'chirilmaydi, lekin xotira chiqariladi! Bundan tashqari, sinflar uyga emas, o'z xotirasiga yuklanadi.
qo'shib qo'ydi muallif radistao, manba
@Andremoniy pls, tavsifni o'qib chiqing va takrorlash bayrog'ini olib tashlang - savol System.gc() chaqirig'iga emas,
qo'shib qo'ydi muallif radistao, manba

6 javoblar

Xato hisobotingiz uchun rahmat. Ushbu xatoni tuzatdik, JDK-8175883 ga qarang. Bu erda kengaytirildi holatlarida sharhlanganidek, javac sintetik o'zgaruvchilardan iborat edi, shuning uchun quyidagi kod uchun:

void foo(String[] data) {
    for (String s : data);
}

javac taxminan ishlab chiqarildi:

for (String[] arr$ = data, len$ = arr$.length, i$ = 0; i$ < len$; ++i$) {
    String s = arr$[i$];
}

Yuqorida aytib o'tilganidek, ushbu tarjima uslubi sintetik o'zgarmaydigan arr $ qatoriga GC-ni ketma-ketlikda to'plashni taqiqlovchi ma'lumotlar usuli. Ushbu kod ushbu kodni ishlab chiqaradi:

String[] arr$ = data;
String s;
for (int len$ = arr$.length, i$ = 0; i$ < len$; ++i$) {
    s = arr$[i$];
}
arr$ = null;
s = null;

The idea is to set to null any synthetic variable of a reference type created by javac to translate the loop. If we were talking about an array of a primitive type, then the last assignment to null is not generated by the compiler. The bug has been fixed in repo JDK repo

6
qo'shib qo'ydi
albatta @radistao, np
qo'shib qo'ydi muallif Vicente Romero, manba
Yangilanishingiz uchun rahmat!
qo'shib qo'ydi muallif radistao, manba
Bu shunchaki tuzatma emas, bu shovqin-a-mol turini. Aksincha, qamoqdan tashqariga chiqadigan har qanday ma'lumotni bekor qilishni istasangiz, bu xakka kerak bo'lmaydi va ishlab chiquvchilar boshqa shunga o'xshash vaziyatlardan xavotir olmaydilar. Kuchsiz nullifikatsiyalar (joylashuv keyinchalik tayinlangan bo'lsa) osongina mikro-optimallashtirish mumkin (o'ng?), Shuning uchun hech qanday ishlash ta'siri bo'lmasligi kerak.
qo'shib qo'ydi muallif RFST, manba
Afsuski, hech qanday yechim (ular doiradan chiqib ketganlarida yoki bu hackda bo'lgan barcha havolalarni bekor qilishadi), istisno qo'yilganda ishlaydi. Yoki buni va nima uchun? BTW, ushbu muammolar C ++ dasturchilariga (RAII qoidalari!) Yuzma-qarshilik hududidir ...
qo'shib qo'ydi muallif RFST, manba

Bu bayonot uchun ishlab chiqilgan yagona qismi - bu ob'ektga qo'shimcha mahalliy mos yozuvlar.

Sizning namunangizni qisqartirish mumkin

public class Example {
    private static final int length = (int) (Runtime.getRuntime().maxMemory() * 0.8);

    public static void main(String[] args) {
        byte[] data = new byte[length];
        Object ref = data;//this is the effect of your "foreach loop"
        data = null;
       //ref = null;//uncommenting this also makes this complete successfully
        byte[] data2 = new byte[length];
    }
}

Ushbu dastur ham OutOfMemoryError bilan ishlamaydi. Agar siz ref deklaratsiyasini (va uni ishga tushirishni) o'chirsangiz, u muvaffaqiyatli bajariladi.

Siz tushunishingiz kerak bo'lgan birinchi narsa: tortib olsa ning axlat yig'ish bilan aloqasi yo'q. tortib olsa - derleme vaqti konsepsiyasi Dasturning identifikatorlari va nomlarini dasturning boshlang'ich kodiga qaerda ishlatilishi mumkinligini belgilaydi.

Garbage collection is driven by reachability. If the JVM can determine that an object cannot be accessed by any potential continuing computation from any live thread, then it will consider it eligible for garbage collection. Also, the System.gc() is useless because the JVM will perform a major collection if it cannot find space to allocate a new object.

Shunday qilib, savol shunday bo'ladi: Nima uchun JVM byte [] ob'ektini ikkinchi mahalliy o'zgaruvchiga saqlab qolsak, unga kirish mumkin emas?

Buning uchun menda javob yo'q. Turli axlat yig'ish algoritmlari (va JVM) bu borada boshqacha munosabatda bo'lishi mumkin. Ushbu JVM mahalliy o'zgaruvchan jadvaldagi ikkinchi kirish ushbu ob'ektga havola kelganida ob'ektni kirib bo'lmaydigan qilib belgilamaydi.


Mana, JVM siz axlat yig'ish uchun kutilganidek o'zingizni tutmagan holda boshqa bir stsenariy:

5
qo'shib qo'ydi
@adistao Identifikatorga kelsak, uni tashlab ketadigan narsa yo'q . Java, mahalliy o'zgarmaydigan hajmini bayt kodidagi berilgan nuqtalarda qaysi o'zgaruvchining faolligini belgilaydigan mahalliy o'zgaruvchan jadvalga (.class faylida) kompilyatsiya qiladi.
qo'shib qo'ydi muallif Sotirios Delimanolis, manba
@radistao Sharh uchun ishlab chiqilgan: bu yerda .
qo'shib qo'ydi muallif Sotirios Delimanolis, manba
@radistao Siz bayt kodini o'qishingiz shart emas. JLS, rivojlangan izoh uchun . Siz taklif qilganingiz kabi, Iterator (sizning ob'ektingizga havola qilingan) ga qo'shimcha havola mavjud.
qo'shib qo'ydi muallif Sotirios Delimanolis, manba
@Sotirios Delimanolis: Yo'q, mahalliy o'zgaruvchilar jadvali ijro uchun ahamiyatga ega bo'lmagan ixtiyoriy disk raskadrovka xususiyati. Mahalliy o'zgaruvchilar mavjudligi faqatgina o'qilgan ma'lumotlar bilan belgilanadi va stack framening joylariga yoziladi. Shuning uchun for döngüsünden sonra iterator başvurusu silkidi: uning o'rnini yangi qiymat bilan yozishga sabab bo'lgan boshqa o'zgaruvchi yo'q. Amalda bu optimallashni boshlagach, o'zingizning foydalanish tahlilini amalga oshiradi, ba'zan esa narsalar siz kutganingizdan ancha oldin ozod bo'lishga olib keladigan darajada ahamiyatsiz bo'ladi. Lekin bu erda, ehtimol, tarjima qilingan
qo'shib qo'ydi muallif Holger, manba
qo'shib qo'ydi muallif radistao, manba
"Endi ikkinchi savol qoladi: foydalanishdan keyin" avtomatik tarzda yaratilgan identifikator "ni tashlash kerakmi (men uchun" ha "degan ma'noni anglatadimi)?
qo'shib qo'ydi muallif radistao, manba
#i - ifodalash uchun ishlab chiqilgan nuqtada (§6.3) o'z ichiga olgan boshqa identifikatorlardan (avtomatik ravishda ishlab chiqarilgan yoki boshqacha) farq qiladigan avtomatik tarzda tuzilgan identifikator. uchun! rahmat! uni alohida javob sifatida bera olasizmi?
qo'shib qo'ydi muallif radistao, manba
"OutOfMemoryError" ga murojaat qilganingiz uchun tashakkursiz kod bloklari sharhlanganidan so'ng - men kodni kodi uchun uchun kodini topishga harakat qilaman (lekin dekompilyatsiya kodlarini o'qishda juda ko'p tajribaga ega emas)
qo'shib qo'ydi muallif radistao, manba
ref obyektidagi bayt qatoriga mos yozuvni saqlayotgan misolda, shunday emasmi? Shuning uchun uni yig'ish mumkin emas. Agar ref deklaratsiyasini olib tashlasam yoki null ga o'rnatilgan bo'lsa, xotira chiqarilishi mumkin ("Siz buni muvaffaqiyatli bajarasiz" deb o'ylayman.) Menimcha, derleyici yoki JVM xotirani qulflaydigan, ammo undan foydalanilmaydigan bironta "o'lik" ma'lumotni yaratish.
qo'shib qo'ydi muallif radistao, manba

Demak, bu aslida bir-biridan farqli so'zlardan foyda olish mumkin bo'lgan qiziqarli savol. Keyinchalik, ishlab chiqarilgan baytokodega e'tiborni qaratib, ko'plab chalkashliklarni yo'qotish mumkin edi. Keling, buni qilaylik.

Ushbu kodni ko'rsating:

List foo = new ArrayList<>();
for (Integer i : foo) {
 //nothing
}

Bu generatsiyalangan baytode hisoblanadi:

   0: new           #2                 //class java/util/ArrayList
   3: dup           
   4: invokespecial #3                 //Method java/util/ArrayList."":()V
   7: astore_1      
   8: aload_1       
   9: invokeinterface #4,  1           //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
  14: astore_2      
  15: aload_2       
  16: invokeinterface #5,  1           //InterfaceMethod java/util/Iterator.hasNext:()Z
  21: ifeq          37
  24: aload_2       
  25: invokeinterface #6,  1           //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
  30: checkcast     #7                 //class java/lang/Integer
  33: astore_3      
  34: goto          15

Shunday qilib, o'yin o'ynash:

  • Yangi ro'yxatni mahalliy o'zgarmaydigan 1da saqlang ("foo")
  • Iteratorni mahalliy o'zgarmaydigan 2da saqlang.
  • Har bir element uchun elementni mahalliy o'zgaruvchida saqlang 3

Ichakdan keyin loopda ishlatilgan hech qanday tozalash yo'qligini ham unutmang. Bu iterator bilan chegaralanmagan: oxirgi element, loop tugagandan keyin ham mahalliy o'zgaruvchida 3 saqlanadi, lekin kodda bu haqda hech qanday ma'lumot yo'q.

Shuning uchun siz "bu noto'g'ri, noto'g'ri, noto'g'ri" ketishdan oldin, yuqoridagi koddan keyin ushbu kodni qo'shganimda nima sodir bo'lishini ko'rib chiqamiz:

byte[] bar = new byte[0];

Ushbu bayt kodini loopdan so'ng olasiz:

  37: iconst_0      
  38: newarray       byte
  40: astore_2      

Oh, bunga qara. Yangi e'lon qilingan mahalliy o'zgarmaydigan iterator bilan bir xil "mahalliy o'zgaruvchida" saqlanadi. Shunday qilib, endi iteratorga qilingan murojaat yo'qoldi.

Shuni e'tiborga olish kerakki, bu Java kodidan farq qiladi. Aynan bytecode hosil qiluvchi haqiqiy Java ekvivalenti quyidagicha:

List foo = new ArrayList<>();
for (Iterator i = foo.iterator(); i.hasNext(); ) {
  Integer val = i.next();
}

Va hali tozalash yo'q. Nima uchun bunday?

Well, here we are in guessing territory, unless it's actually specified in the JVM spec (haven't checked). Anyway, to do cleanup, the compiler would have to generate extra bytecode (2 instructions, aconst_null and astore_) for each variable that's going out of scope. This would mean the code runs slower; and to avoid that, possibly complicated optimizations would have to be added to the JIT.

Xo'sh, nima uchun kodingiz muvaffaqiyatsiz tugadi?

Siz yuqorida keltirilgan holatga o'xshash vaziyatga tushasiz. Iterator mahalliy o'zgaruvchiga ajratiladi va saqlanadi. So'ngra sizning kodingiz yangi magistral qatorini ajratishga harakat qiladi va mahalliy o'zgaruvchi 1 endi ishlatilmayotganligi sababli u bir xil mahalliy o'zgaruvchida saqlanadi (baytobodni tekshiring). Lekin ajratish topshiriqdan oldin sodir bo'ladi, shuning uchun iteratorga hali murojaatnoma bor, shuning uchun xotira yo'q.

Agar try blokidan oldin bu qatorni qo'shsangiz, System.gc() chaqiruvini olib tashlasangiz ham, ishlar bajariladi:

int i = 0;

Ya'ni, JVM ishlab chiquvchilari o'zlari tanlagan (kichikroq/yanada samarali bayt kodini ishlab chiqaradilar), va ular sizning odamlaringiz haqida qanday taxminlarga kod yozish. Amalda bu muammoni hech qachon ko'rmaganligim sababli, men uchun kichik bir narsa kabi ko'rinadi.

4
qo'shib qo'ydi
ammo: 1) agar burchak ishi bo'lsa - hisobga olinishi kerak (yaxshi ishlab chiquvchi burchaklarni tashlab ketadimi?); 2) platforma (derleyici + VM) aniqlanmagan tarzda ishlamasligi kerak; 3) barqarorlik va determinizmdan "ko'proq samarali" qurbon bo'lishga qaror qilish yaxshi tanlov emas; 4) katta "XML" faylini ajratib olish va uni yechib bo'lgach, men xotirada qolib ketganimdan keyin ishlaydigan ilovada topilgan "burchak ishi".
qo'shib qo'ydi muallif radistao, manba
bu men uchun eng to'g'ri javobni qidiradi: iterator moslamasi uchun "avtomatik tarzda yaratilgan identifikator" identifikatorning keyingi foydalanishgacha xotirani saqlaydi.
qo'shib qo'ydi muallif radistao, manba

Boshqa javoblarda ham aytib o'tilganidek, u erda o'zgaruvchan o'lchov kontseptsiyasi ish vaqtida ma'lum emas. Tarkib qilingan sinf fayllarida, mahalliy o'zgaruvchilar faqat yozuvchi va o'qiladigan fayllarni bajaradigan (yig'indisi) indeksdagi joylardir. Agar bir nechta o'zgaruvchining bo'sh joylari mavjud bo'lsa, ular bir xil indeksdan foydalanishi mumkin, ammo rasmiy bayonotlar yo'q. Faqat yangi qiymatni yozish sobiqni yo'q qiladi.

Shunday qilib, uch xil usul mavjud: mahalliy o'zgaruvchan saqlashda qo'llanilgan qo'llanmani qo'llanilmagan deb hisoblash mumkin:

  1. Saqlash joyi yangi qiymat bilan yoziladi
  2. usuli
  3. dan chiqadi
  4. Keyingi kod hech qanday qiymatni o'qiy olmaydi

Uchinchi nuqta tekshirishning eng qiyin usuli ekanligi aniq, shuning uchun u har doim ham qo'llanilmaydi, lekin optimizauchunr o'z ishini boshlaganida, " JAVA ob'ektni hali uchunrtib olsa bo'lganda yakunlashi mumkinmi? " va " finalized() Java 8-da kuchli mos keluvchi obyektni chaqirdi ."

Sizning holatlaringizda, arizangiz juda qisqa muddatda va optimallashtirilmagan bo'lishi mumkin, natijada 3 va 3-bandlar uchun qo'llanilmaydi.

Buni osonlik bilan tekshirib ko'rishingiz mumkin. Chiziqni o'zgartirganda

ArrayList strings2 = generateLargeStringsArray(N);

uchun

ArrayList strings2 = null;
strings2 = generateLargeStringsArray(N);

the OuuchunfMemoryError goes away. The reason is that the suchunrage location holding the Iterauchunr used in the preceding for loop has not been overwritten at this point. The new local variable strings2 will reuse the suchunrage, but this only manifests when a new value is actually written uchun it. So the initialization with null before calling generateLargeStringsArray(N) will overwrite the Iterauchunr reference and allows the old list uchun be collected.

Alternatively, you can run the program in the original form using the option -Xcomp. This forces compilation of all methods. On my machine, it had a noticeable startup slowdown, but due uchun the variable usage analysis, the OuuchunfMemoryError also went away.

Having an application that allocates that much memory (compared uchun the max heap size) during the initialization, i.e. when most methods run interpreted, is an unusual corner case. Usually, most hot methods are sufficiently compiled before the memory consumption is that high. If you encounter this corner case repeatedly in a real life application, well, then -Xcomp might work for you.

3
qo'shib qo'ydi

Nihoyat, Oracle/Open JKD xatosi qabul qilinadi, tasdiqlanadi va o'rnatiladi:

http://bugs.java.com/bugdatabase/view_bug.do? bug_id = JDK-8175883

https://bugs.openjdk.java.net/browse/JDK-8175883

Mavzulardagi fikrlarni keltirib:

Bu muammo 8 va 9-da takrorlanadigan

     

Dasturning o'ziga xos yopiq avtomatik tarzda yaratilganligi haqida ba'zi muammolar mavjud   keyingi yopiq foydalanishgacha va uning xotirasiga qadar xotira blokiga murojaat qiling   OOMga sabab bo'lgan qulflangan.

(this proves @vanza's expectation, see this example from the JDK developer)

Maslahatlarga ko'ra bu amalga oshmasligi kerak

(bu mening savolimga javob: agar kompilyator ba'zi bir misollarni keltirib chiqargan bo'lsa, ularni dan keyin ularni qo'llashdan ehtiyot bo'lish kerak)

UPD 21.11.2017: the bug is fixed in JDK, see comment from Vicente Romero

1
qo'shib qo'ydi

Faqat javoblarni umumlashtirish uchun:

@ Sotirios-delimanolis uning izohida Sharh uchun ishlab chiqilgan </</code> - Iterator ga tegishli hasNext() - Keyingi() shakar iborasi uchun /code> qo'ng'iroqlari:

#i - bu o'z ichiga olgan avtomatik identifikatorlardan (avtomatik tarzda ishlab chiqilgan yoki boshqacha) avtomatik ravishda tuzilgan identifikator (
§6.3 ).

So'ng @vanza uning javobida ko'rsatdi : avtomatik ravishda yaratilgan identifikator yoki undan keyin bekor qilinmagan bo'lishi mumkin. Agar u o`zgartirilgan bo`lsa - xotira chiqarilishi mumkin, agar bo'lmasa - xotira endi chiqarilmaydi.

Hali ham (men uchun) ochiq savol: agar Java derivati ​​yoki JVM ba'zi yopiq murojaatlarni yaratsa, bundan keyin bu havolalarni bekor qilish haqida qayg'urmaslik kerakmi? Keyingi avtomatlashtirilgan iterator ma'lumotnomasi navbatdagi chaqiruvlarda keyingi xotira ajratishdan oldin qayta foydalanishga kafolat bormi? ? Bu qoida bo'lishi kerak emasmi? Xotirani ajratadiganlar uni ozod qilish haqida g'amxo'rlik qiladilarmi? Men aytmoqchimanki, bu haqda kerak . Aks holda xatti-harakatlar aniqlanmagan (OutOfMemoryError-ga tushishi mumkin, yoki bo'lmasa - kim biladi ...)

Ha, mening misolim burchakli ishdir ( for for iterator va keyingi xotira ajratish), lekin bu imkonsiz degani emas. Bu esa, bu ishni bajarish qiyin emas degani emas - bu juda katta ma'lumotlar bilan chegaralangan xotira muhitida ishlash va uni darhol qayta ishlatish ehtimoli. Ushbu ishni mening ishlaydigan ilovada topdim, unda xotiraning yarmidan ko'prog'ini "egan" katta xml ajratish mumkin.

(Va savol faqatgina iterator va for loops uchun emas, balki u umumiy muammo hisoblanadi: derleyici yoki JVM ba'zan o'z yopiq murojaatlarni tozalash emas).

0
qo'shib qo'ydi
Xo'sh, men javobimda aytilganidek, agar siz ushbu noodatiy stsenariyga duch kelsangiz va u xotira bosimiga ega bo'lsangiz, uni sozlash uchun ba'zi CPU davrlarini sotish imkonini beruvchi variant bor. Sizning ishingiz odatiy holdir, chunki u katta ro'yxat hosil qiladi, uni bir marta yechadi va undan keyin tushadi. Odatda, ro'yxatlar ispektorga qaraganda ancha uzoqroq muddatga ega, va bu yutib yuboradigan qurilmaning xotirasi emas.
qo'shib qo'ydi muallif Holger, manba
Ammo agar o'ylayotgan bo'lsangiz, bu xato, siz Stackoverflow-da uni muhokama qilishdan ko'ra, xato haqida xabar berishingiz mumkin ...
qo'shib qo'ydi muallif Holger, manba
Foydalanilmaydigan xotirani ozod qilish va'dasi faqat eng yaxshi harakat asosida amalga oshiriladi. Endi esa, bu haqda o'ylab ko'ring: Agar Java bu xotirani hech qachon ozod qila olmasa, bu taxmin qilinadigan xatti-harakat bo'ladi. Ammo ayrim JVMlar ma'lum xotirani muayyan sharoitlarda ozod qilishni boshlaydilar, shuning uchun vaziyatni yaxshilash yoki yomonlashmoqdami? Bundan tashqari, hech qanday xotira sarfi yo'q. Boshqa JVM har doim bu xotirani chiqarishga muvaffaq bo'la oladi, lekin umuman, har bir obyekt uchun xotiradan ikki martadan foydalanadi, shuning uchun ham o'sha kod bilan ishlamaydi. Bu vaziyatni yaxshilashmi? kerak bo'lgan qancha xotira dasturga xosdir.
qo'shib qo'ydi muallif Holger, manba
Menimcha, agar "java-ilovasi prognozli tarzda ishlashi kerak" deb hisoblasangiz va xotira iste'moli va ishlashi haqida gapiradigan bo'lsak, Java siz uchun noto'g'ri narsa bo'lishi mumkin. Xotirani zudlik bilan ozod qilishni qabul qila olmaysiz, maksimal rekursiya chuqurligini taxmin qila olmaysiz va ishlash haqida kafolat yo'q. Bu erda buzilgan tamoyillarda hech qanday kafolat yo'q. Bundan tashqari, yuqorida aytib o'tilganidek, kodingiz -Xcomp bilan ishlayotgan bo'lsangiz kutilganidek ishlaydi.
qo'shib qo'ydi muallif Holger, manba
-Xcomp "standart bo'lmagan variantlar" ( docs. oracle.com/javase/8/docs/technotes/tools/unix/java.htm‌ l ), shuning uchun turli xil JVM ilovalari uchun turli izohlarga ega bo'lishi mumkin (bu erda eskirgan: ibm.com/ support/knowledgecenter/SSYKE2_8.0.0/& hellip; )
qo'shib qo'ydi muallif radistao, manba
Va nima uchun "java dasturining taxminiy usulda ishlashi" haqidagi taxminni nima uchun noto'g'ri eshitib turibman? Barchamiz bashorat qilinadigan dasturlardan foydalanishni istaymiz - bu barqarorlikning asosiy nuqtasidir. "Performance", "recursion", "compaction" va "instant release" haqida hech narsa yo'q - Java ishlatilmaydigan xotirani bo'shatishni va'da qilmoqda. Ushbu xotira qo'llanilmaydi va chop etilmaydi. Agar JVM bo'shatish uchun vaqt kerak bo'lsa - uni oling, lekin shikoyat qilmang "ishlab chiquvchi oldindan bilib bo'lmaydigan kodlash uslubiga ega va men bilan avtomatik bog'langan murojaatlarni qayta ishlatmagan"
qo'shib qo'ydi muallif radistao, manba
Hatto burchak ishi bo'lsa - uni to'g'ri ishlashi kerak. Ilovani yaratgandan so'ng, siz burchakni ishlov berish va sinovdan o'tkazasizmi? Java ilovalari kabi narsalar ham kamdan-kam holatlarda oldindan ma'lum darajada ishlashi kerak. Va "rezyumeni" boshqa chaqiriqlar orqali qayta ishlatilmasligi mumkin yoki bo'lmasligi kerak ". Shuningdek, avtomatik ravishda yaratilgan mos yozuvlar qayta ishlatilishini kafolatlamaydi va xotiradan chiqarilgan xotira! Men bu ishni amaliyotda topdim, shuning uchun bu erda muammoni hal qilaman.
qo'shib qo'ydi muallif radistao, manba
JDK ishlab chiquvchilaridan tasdiqlashni kutib o'tirdim :)
qo'shib qo'ydi muallif radistao, manba
@Holger nihoyat xatoni qabul qilsa, sharhni ko'ring: stackoverflow.com/a/42556668/907576 . Men ularni to'g'ridan-to'g'ri yozolmayapman, chunki -Xcomp variantini tanlashingiz mumkin, chunki muammolarni izlovchi maxsus va davlat ro'yxatga olinmagan. Ularni e-pochta orqali yuborishga harakat qilamiz.
qo'shib qo'ydi muallif radistao, manba