Сокращение оттока памяти при обработке большого набора данных - PullRequest
1 голос
/ 31 декабря 2011

Java имеет тенденцию создавать большое количество объектов, которые необходимо собирать при обработке большого набора данных. Это происходит довольно часто при потоковой передаче большого количества данных из базы данных, создании отчетов и т. Д. Существует ли стратегия уменьшения оттока памяти.

В этом примере объектно-ориентированная версия тратит значительное количество раз (2+ секунды) на создание объектов и сборку мусора, тогда как версия логического массива завершается за часть раздела без какой-либо сборки мусора.

Как уменьшить отток памяти (необходимость в большом количестве сборщиков мусора) при обработке больших наборов данных?

java -verbose:gc -Xmx500M UniqChars
...
----------------
[GC 495441K->444241K(505600K), 0.0019288 secs] x 45 times
70000007
================
70000007



import java.util.HashSet;
import java.util.Set;
public class UniqChars {
    static String a=null;
    public static void main(String [] args) {
            //Generate data set
            StringBuffer sb=new StringBuffer("sfdisdf");
            for (int i =0; i< 10000000; i++) {
                    sb.append("sfdisdf");
            }
            a=sb.toString();
            sb=null;  //free sb
            System.out.println("----------------");
            compareAsSet();
            System.out.println("================");
            compareAsAry();
    }

    public static void compareAsSet() {
            Set<String> uniqSet = new HashSet<String>();
            int n=0;
            for(int i=0; i<a.length(); i++) {
                    String chr = a.substring(i,i);
                    uniqSet.add(chr);
                    n++;
            }
            System.out.println(n);
    }

    public static void compareAsAry() {
            boolean uniqSet[] = new boolean[65536];
            int n=0;
            for(int i=0; i<a.length(); i++) {
                    int chr = (int) a.charAt(i);
                    uniqSet[chr]=true;
                    n++;
            }
            System.out.println(n);
    }
}

Ответы [ 3 ]

4 голосов
/ 31 декабря 2011

Ну, как указано в одном из комментариев, это ваш код, а не Java виноват в оттоке памяти.Итак, давайте посмотрим, что вы написали этот код, который создает безумно большую строку из StringBuffer.Вызывает toString () для него.Затем вызывает substring () для этой безумно большой строки, которая находится в цикле, и создает новые строки a.length ().Затем делает некоторые ненужные в массиве, которые действительно будут работать чертовски быстро, так как объект не создается, но в конечном итоге записывает в истину те же 5-6 мест в огромном массиве.Тратить много?Так что вы думаете, что произойдет?Отключите StringBuffer и используйте StringBuilder, поскольку он не полностью синхронизирован, что будет немного быстрее.

Хорошо, вот где ваш алгоритм, вероятно, тратит свое время.Посмотрите, как StringBuffer выделяет внутренний массив символов для хранения вещей каждый раз, когда вы вызываете append ().Когда этот массив символов полностью заполняется, он должен выделить больший массив символов, скопировать весь этот мусор, который вы только что написали ему, в новый массив, а затем добавить то, с чем вы изначально его называли.Таким образом, ваш код выделяет заполнение, выделяет больший кусок, копирует этот мусор в новый массив, а затем повторяет этот процесс до тех пор, пока он не сделает это 1000000 раз.Вы можете ускорить это, предварительно выделив массив символов для StringBuffer.Примерно это 10000000 * "sfdisdf" .length ().Это не даст Java создавать тонны памяти, которые она просто сбрасывает снова и снова.

Далее следует беспорядок compareAsSet ().Ваша строка String chr = a.substring (i, i);создает новые строки a.length () раз.Ну, так как вы используете a.substring (i, i) - это только символ, который вы могли бы просто использовать charAt (i), тогда выделение не происходит.Также есть опция CharSequence, которая не создает новую строку с собственным массивом символов, а просто указывает на исходный базовый символ [] со смещением и длиной.String.subSequence ()

Вы подключаете этот же код к любому другому языку, и он тоже будет отстой.На самом деле я бы сказал, гораздо хуже.Просто попробуйте это C ++ и посмотрите, будет ли оно значительно хуже, чем Java, если вы выделите и освободите это так много.См. Распределение памяти Java намного быстрее, чем C ++, потому что все в Java выделяется из пула памяти, поэтому создание объектов происходит на порядок быстрее.Но есть пределы.Кроме того, Java сжимает память, если она становится слишком фрагментированной, а C ++ - нет.Таким образом, по мере того, как вы выделяете память и выкидываете ее, точно так же, вы, вероятно, рискуете фрагментировать память в C ++.Это может означать, что ваш StringBuffer может исчерпать способность расти достаточно большим, чтобы закончить, и может произойти сбой.

Фактически, это также может объяснить некоторые проблемы с производительностью GC, потому что он должен сделать пространство более непрерывным блоком, достаточно большим после удаления большого количества мусора.Так что Java не только очищает память, но и сжимает адресное пространство памяти, чтобы он мог получить достаточно большой блок для вашего StringBuffer.

В любом случае, я уверен, что вы просто тестируете шины, но тестируетес таким кодом не очень хорошо, потому что он никогда не будет работать хорошо, потому что это нереальное распределение памяти.Вы знаете старую пословицу «Мусор в мусоре».И вот что у тебя есть Мусор.

4 голосов
/ 31 декабря 2011

В вашем примере ваши два метода делают совершенно разные вещи.

В compareAsSet() вы генерируете одинаковые 4 строки ("s", "d", "f" и "i") ивызов String.hashCode () и String.equals (String) (HashSet делает это при попытке добавить их) 70000007 раз.В итоге получается HashSet размера 4. При этом вы распределяете объекты String каждый раз, когда возвращается String.substring (int, int), что заставляет небольшую коллекцию каждый раз при «новом» поколении сборщика мусора.заполняется.

В compareAsAry() вы выделили один массив шириной 65536 элементов, изменив некоторые значения в нем, а затем он выходит из области видимости, когда метод возвращается.Это одна операция с кучей памяти против 70000007, выполненная в compareAsSet.У вас есть локальная переменная int, которая изменяется 70000007 раз, но это происходит в памяти стека, а не в куче памяти.Этот метод на самом деле не генерирует столько мусора в куче по сравнению с другим методом (в основном это просто массив).

Что касается оттока, то ваши параметры - это переработка объектов или настройка сборщика мусора.

Переработка на самом деле невозможна со строками в общем, поскольку они неизменяемы, хотя виртуальная машина может выполнять операции интернирования, это только уменьшает общий объем памяти, а не отток мусора.Решение, предназначенное для вышеупомянутого сценария, которое может быть сгенерировано повторно, но реализация будет хрупкой и негибкой.

Настройка сборщика мусора таким образом, чтобы «новое» поколение было больше, могло бы уменьшить общее количество сборок, которое должноБыть выполненным во время вашего вызова метода и, таким образом, увеличить пропускную способность вызова, вы также можете просто увеличить размер кучи в целом, что позволило бы выполнить то же самое.

Для дальнейшего чтения при настройке сборщика мусора в Java 6, я рекомендуюофициальный документ Oracle, связанный ниже.

http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html

1 голос
/ 31 декабря 2011

Для сравнения, если бы вы написали это, он сделал бы то же самое.

public static void compareLength() {
    // All the loop does is count the length in a complex way.
    System.out.println(a.length());
}

// I assume you intended to write this.
public static void compareAsBitSet() {
    BitSet uniqSet = new BitSet();
    for(int i=0; i<a.length(); i++)
        uniqSet.set(a.charAt(i));
    System.out.println(uniqSet.size());
}

Примечание: BitSet использует 1 бит на элемент, а не 1 байт на элемент. Он также расширяется по мере необходимости, скажем, у вас есть текст ASCII, BitSet может использовать 128-битные или 16-байтовые (плюс 32-байтовые служебные данные). Логическое значение [] использует 64 КБ, что намного выше. По иронии судьбы, использование boolean[] может быть быстрее, поскольку оно требует меньшего сдвига битов и только часть используемого массива должна находиться в памяти.

Как видите, с любым решением вы получите гораздо более эффективный результат, потому что вы используете лучший алгоритм для того, что нужно сделать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...