Pertanyaan Pendarahan memori masif yang menyebabkan ukuran heap naik dari sekitar 64mb, menjadi 1,5gb dalam waktu sekitar 8 detik. Masalah dengan pengumpul sampah?


Inilah masalahnya:

the memory usage balloons out of control

Seperti yang Anda lihat, balon penggunaan memori di luar kendali! Saya harus menambahkan argumen ke JVM untuk meningkatkan heapsize hanya untuk menghindari kesalahan memori ketika saya mencari tahu apa yang terjadi. Tidak baik!

Ringkasan Aplikasi Dasar (untuk konteks)

Aplikasi ini (akhirnya) akan digunakan untuk dasar di layar CV dan jenis pencocokan template untuk keperluan otomatisasi. Saya ingin mencapai frame rate setinggi mungkin untuk menonton layar, dan menangani semua proses melalui serangkaian utas konsumen yang terpisah.

Saya segera mengetahui bahwa kelas Robot saham benar-benar mengerikan, jadi saya membuka sumbernya, mengambil semua upaya yang telah digandakan dan membuang biaya overhead, dan membangunnya kembali sebagai kelas saya sendiri yang disebut FastRobot.

Kode Kelas:

public class FastRobot {
    private Rectangle screenRect;
    private GraphicsDevice screen;
    private final Toolkit toolkit;
    private final Robot elRoboto;
    private final RobotPeer peer;
    private final Point gdloc;
    private final DirectColorModel screenCapCM;
    private final int[] bandmasks;

    public FastRobot() throws HeadlessException, AWTException {
        this.screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
        this.screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        toolkit = Toolkit.getDefaultToolkit();
        elRoboto = new Robot();
        peer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);

        gdloc = screen.getDefaultConfiguration().getBounds().getLocation();
        this.screenRect.translate(gdloc.x, gdloc.y);

        screenCapCM = new DirectColorModel(24,
                /* red mask */    0x00FF0000,
                /* green mask */  0x0000FF00,
                /* blue mask */   0x000000FF);
        bandmasks = new int[3];
        bandmasks[0] = screenCapCM.getRedMask();
        bandmasks[1] = screenCapCM.getGreenMask();
        bandmasks[2] = screenCapCM.getBlueMask();

        Toolkit.getDefaultToolkit().sync();
    }

    public void autoResetGraphicsEnv() {
        this.screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
        this.screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
    }

    public void manuallySetGraphicsEnv(Rectangle screenRect, GraphicsDevice screen) {
        this.screenRect = screenRect;
        this.screen = screen;
    }


    public BufferedImage createBufferedScreenCapture(int pixels[]) throws HeadlessException, AWTException {
//      BufferedImage image;
        DataBufferInt buffer;
        WritableRaster raster;

        pixels = peer.getRGBPixels(screenRect);
        buffer = new DataBufferInt(pixels, pixels.length);

        raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
        return new BufferedImage(screenCapCM, raster, false, null);
    }

    public int[] createArrayScreenCapture() throws HeadlessException, AWTException {
            return peer.getRGBPixels(screenRect);
    }

    public WritableRaster createRasterScreenCapture(int pixels[]) throws HeadlessException, AWTException {
    //  BufferedImage image;
        DataBufferInt buffer;
        WritableRaster raster;

        pixels = peer.getRGBPixels(screenRect);
        buffer = new DataBufferInt(pixels, pixels.length);

        raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
    //  SunWritableRaster.makeTrackable(buffer);
        return raster;
    }
}

Intinya, semua yang telah saya ubah dari aslinya memindahkan banyak alokasi dari fungsi tubuh, dan mengaturnya sebagai atribut kelas sehingga mereka tidak dipanggil setiap waktu. Melakukan hal ini sebenarnya memiliki penting mempengaruhi pada frame rate. Bahkan pada laptop saya yang sangat bertenaga, ia pergi dari ~ 4 fps dengan kelas Robot saham, ke ~ 30fps dengan kelas FastRobot saya.

Tes Pertama:

Ketika saya mulai melakukan kesalahan kesalahan dalam program utama saya, saya menyiapkan tes yang sangat sederhana ini untuk mengawasi FastRobot. Catatan: ini adalah kode yang menghasilkan profil heap di atas.

public class TestFBot {
    public static void main(String[] args) {
        try {
            FastRobot fbot = new FastRobot();

            double startTime = System.currentTimeMillis();
            for (int i=0; i < 1000; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

        } catch (AWTException e) {
            e.printStackTrace();
        }
    }
}

Diperiksa:

Itu tidak melakukan ini setiap waktu, yang benar-benar aneh (dan membuat frustrasi!). Bahkan, jarang melakukannya sama sekali dengan kode di atas. Namun, masalah memori menjadi dengan mudah direproduksi jika saya memiliki beberapa untuk loop kembali ke belakang.

Tes 2

public class TestFBot {
    public static void main(String[] args) {
        try {
            FastRobot fbot = new FastRobot();

            double startTime = System.currentTimeMillis();
            for (int i=0; i < 1000; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

            startTime = System.currentTimeMillis();
            for (int i=0; i < 500; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

            startTime = System.currentTimeMillis();
            for (int i=0; i < 200; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

            startTime = System.currentTimeMillis();
            for (int i=0; i < 1500; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

        } catch (AWTException e) {
            e.printStackTrace();
        }
    }
}

Diperiksa

Tumpukan yang tidak terkendali sekarang dapat direproduksi, saya katakan sekitar 80% dari waktu. Saya telah melihat semua profiler, dan hal yang paling penting (saya pikir) adalah bahwa tukang sampah tampaknya berhenti kanan sebagai lingkaran keempat dan terakhir dimulai.

Output dari kode di atas memberikan waktu berikut:

Time taken: 24.282    //Loop1
Time taken: 11.294    //Loop2
Time taken: 7.1       //Loop3
Time taken: 70.739    //Loop4

Sekarang, jika Anda menjumlahkan tiga putaran pertama, ia menambahkan hingga 42.676, yang secara mencurigakan sesuai dengan waktu yang tepat saat pengumpul sampah berhenti, dan lonjakan memori.

enter image description here

Sekarang, ini adalah rodeo pertama saya dengan profil, belum lagi pertama kalinya saya pernah berpikir tentang pengumpulan sampah - itu selalu sesuatu yang hanya bekerja ajaib di latar belakang - jadi, saya tidak yakin apa, jika ada, saya sudah tahu.

Informasi Profil Tambahan

enter image description here

Augusto menyarankan untuk melihat profil ingatannya. Ada 1500+ int[] yang terdaftar sebagai "tidak dapat dijangkau, tetapi belum dikumpulkan." Ini tentu saja int[] array yang peer.getRGBPixels() menciptakan, tetapi untuk beberapa alasan mereka tidak dihancurkan. Info tambahan ini, sayangnya, hanya menambah kebingungan saya, karena saya tidak yakin Mengapa GC tidak akan mengumpulkannya


Profil menggunakan argumen tumpukan kecil -Xmx256m:

Pada saran yang tidak dapat ditolak dan Hot Licks, saya menetapkan ukuran tumpukan maksimal ke sesuatu yang jauh lebih kecil. Saat ini tidak mencegahnya membuat lompatan 1gb dalam penggunaan memori, itu masih tidak menjelaskan Mengapa program ini menggelembung ke ukuran tumpukan maksimal setelah memasuki iterasi ke-4.

enter image description here

Seperti yang Anda lihat, masalah yang sebenarnya masih ada, baru saja dibuat lebih kecil. ;) Masalah dengan solusi ini adalah bahwa program, karena alasan tertentu, masih makan melalui semua memori yang dapat - ada juga perubahan yang ditandai dalam kinerja fps dari iterasi pertama, yang mengkonsumsi sedikit memori, dan iterasi terakhir, yang menghabiskan memori sebanyak mungkin.

Pertanyaannya tetap Mengapa apakah itu menggelembung?


Hasil setelah menekan tombol "Paksa Pengumpulan Sampah":

Atas saran jtahlborn, saya memukul Paksa Koleksi Sampah tombol. Ini bekerja dengan sangat baik. Mulai dari penggunaan memori 1gb, hingga ke baseline 60mb atau lebih.

enter image description here

Jadi, ini sepertinya obatnya. Pertanyaannya sekarang adalah, bagaimana saya secara pemrograman memaksa GC melakukan ini?


Hasil setelah menambahkan Peer lokal ke ruang lingkup fungsi:

Atas saran David Waters, saya memodifikasi createArrayCapture() berfungsi sehingga memegang lokal Peer obyek.

Sayangnya tidak ada perubahan dalam pola penggunaan memori.

enter image description here

Masih sangat besar pada iterasi ke-3 atau ke-4.


Analisis Kumpulan Memori:

ScreenShots dari kolam memori yang berbeda

Semua kolam:

All pools

Kolam Eden:

Eden Pool

Gen Tua:

Old Gen
Hampir semua penggunaan memori tampaknya jatuh di kolam ini.

Catatan: PS Survivor Space memiliki (ternyata) 0 penggunaan


Saya pergi dengan beberapa pertanyaan:

(a) Apakah grafik Profiler Sampah berarti apa yang saya pikir artinya? Atau saya membingungkan korelasi dengan sebab-akibat? Seperti yang saya katakan, saya berada di daerah yang tidak dikenal dengan masalah ini.

(B) Jika itu aku s pengumpul sampah ... apa yang harus saya lakukan tentang itu ..? Mengapa berhenti sama sekali, dan kemudian berjalan pada tingkat yang dikurangi untuk sisa program?

(c) Bagaimana saya memperbaiki ini?

Apa yang terjadi di sini?


32
2018-02-28 16:46


asal


Jawaban:


Coba tentukan pengumpul sampah secara manual.

Serentetan Tanda Serentak adalah salah satu tujuan umum yang baik yang memberikan keseimbangan yang baik antara jeda rendah dan throughput yang masuk akal.

Jika Anda berada di Java 7 atau lebih baru Java 6, kolektor G1 mungkin lebih baik karena juga dapat mencegah fragmentasi memori.

Anda dapat memeriksa Java SE Hotspot Virtual Machine Sampah Koleksi Tuning halaman untuk informasi lebih lanjut dan petunjuk :-D


4
2018-03-02 11:13



Anda mengatakan Anda memindahkan pembuatan objek dari metode ke bidang di kelas. Apakah salah satu dependensi Anda pindah "peer"? misalnya

peer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);

Bisa jadi bahwa rekan memegang pada semua screenshot diambil untuk kehidupan objek, ini akan dibersihkan ketika rekan pindah dari ruang lingkup, akhir metode dalam Robot, kehidupan kelas untuk FastRobot.

Coba pindahkan pembuatan dan ruang lingkup rekan kembali ke metode Anda dan lihat apa perbedaannya.

public int[] createArrayScreenCapture() throws HeadlessException, AWTException {
        RobotPeer localPeer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);
        return localPeer.getRGBPixels(screenRect);
}

Mencoba 2

Jadi, ini sepertinya obatnya. Pertanyaannya sekarang adalah, bagaimana cara saya pro   gramatikal memaksa GC melakukan ini?

Anda dapat memanggil System.gc () untuk meminta pengumpulan sampah. Perhatikan ini adalah permintaan, bukan permintaan. JVM hanya akan menjalankan pengumpulan sampah jika dianggap bernilai sementara.

Seperti yang Anda lihat, masalah sebenarnya masih ada, baru saja dibuat   lebih kecil. ;) Masalah dengan solusi ini adalah bahwa program, untuk beberapa   alasannya, masih makan melalui semua memori yang dapat - ada   juga perubahan yang ditandai dalam kinerja fps dari iterasi pertama,   yang mengkonsumsi sedikit memori, dan iterasi terakhir, yang   mengkonsumsi sebanyak mungkin memori.

Pertanyaannya tetap mengapa itu membengkak sama sekali?

JVM akan mencoba dan hanya menjalankan Koleksi Sampah Utama ketika benar-benar diperlukan (sebagian besar tumpukan digunakan). (Baca ke Generasi Muda vs Generasi Lama dan dalam Generasi Muda, ruang Eden dan ruang selamat). Jadi berharap aplikasi java hidup lama atau memori lapar untuk duduk di dekat ukuran heap maksimum. Perlu dicatat bahwa untuk memori untuk pindah ke Generasi Lama itu harus selamat dari 3 GC berjalan kecil (Eden => Survivor 1 => Survivor 2 => Generasi Lama [Tergantung pada apa JVM Anda menjalankan dan skema GC apa yang Anda selcted pada argumen baris perintah.])

Adapun mengapa perilaku ini berubah, itu bisa menjadi sejumlah hal. Loop terakhir ini adalah yang terpanjang, apakah System.getCurrentTimeMillis () memblokir cukup lama untuk GC untuk beralih ke thread yang berbeda? Jadi masalahnya hanya muncul di loop yang lebih panjang? Proses pengambilan suara screen shot cukup rendah untuk saya, saya berasumsi diimplementasikan dengan panggilan ke sistem operasi kernal, apakah ini memblokir proses di ruang kernal mencegah benang lain dari berjalan? (yang akan menghentikan gc yang berjalan di latar belakang).

Silahkan lihat http://www.javacodegeeks.com/2012/01/practical-garbage-collection-part-1.html untuk pengenalan dunia pengumpulan sampah. Atau Memori Java dijelaskan (SUN JVM) untuk menimbun lebih banyak tautan.

Harapan yang telah membantu.


3
2018-02-28 17:46