Java VM неожиданно завершает работу без видимой причины - PullRequest
2 голосов
/ 28 августа 2009

У меня проблема с внезапным завершением работы моей Java-программы, без каких-либо исключений, или когда программа нормально завершает работу.

Я пишу программу для решения Project Euler 14-й проблемы . Вот что я получил:

private static final int INITIAL_CACHE_SIZE = 30000;
private static Map<Long, Integer> cache = new HashMap<Long, Integer>(INITIAL_CACHE_SIZE);

public void main(String... args) {
    long number = 0;
    int maxSize = 0;

    for (long i = 1; i <= TARGET; i++) {
        int size = size(i);
        if (size > maxSize) {
            maxSize = size;
            number = i;
        }
    }
}
private static int size(long i) {
    if (i == 1L) {
        return 1;
    }
    final int size = size(process(i)) + 1;
    return size;
}

private static long process(long n) {
    return n % 2 == 0 ? n/2 : 3*n + 1;
}

Это работает нормально и завершается правильно примерно через 5 секунд при использовании ЦЕЛИ 1 000 000.

Я хотел оптимизировать, добавив кеш, поэтому я изменил метод размера следующим образом:

private static int size(long i) {
    if (i == 1L) {
        return 1;
    }
    if (cache.containsKey(i)) {
        return cache.get(i);
    }
    final int size = size(process(i)) + 1;
    cache.put(i, size);
    return size;
}

Теперь, когда я запускаю его, он просто останавливается (процесс завершается), когда я добираюсь до 555144. Каждый раз одно и то же число. Не исключение, ошибка, сбой Java VM или что-либо другое.

Изменение размера кеша, похоже, тоже не имеет никакого эффекта, так как кеш может Введение причиной этой ошибки?

Если я сделаю так, чтобы размер кэша был не просто начальным, а постоянным, вот так:

    if (i < CACHE_SIZE) {
        cache.put(i, size);
    }

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

Может кто-нибудь воспроизвести это, и, возможно, даже предложить, почему это происходит?

Ответы [ 7 ]

8 голосов
/ 28 августа 2009

Это просто ошибка OutOfMemoryError, которая не печатается. Программа работает нормально, если я установил большой размер кучи, в противном случае она завершается с незафиксированным OutOfMemoryError (хотя это легко увидеть в отладчике).

Вы можете проверить это и получить дамп кучи (а также распечатать, что произошла ошибка OutOfMemoryError), передав этот аргумент JVM и повторно запустив вашу программу:

-XX: + HeapDumpOnOutOfMemoryError

После этого он распечатает что-то на этот счет:

java.lang.OutOfMemoryError: пространство кучи Java
Сброс кучи в java_pid4192.hprof ...
Создан файл дампа кучи [91901809 байт за 4,464 секунды]

Увеличьте размер кучи, скажем, -Xmx200m, и у вас не возникнет проблем - по крайней мере, для TARGET = 1000000.

3 голосов
/ 28 августа 2009

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

При этом некоторые ошибки OutOfMemory не отображаются в главном потоке, поэтому, если вы не сделаете попытку / поймать (Throwable t) и не убедитесь, что вы ее получили, трудно быть уверенным, что вы на самом деле не просто не хватает памяти. Тот факт, что он использует только 100 МБ, может означать, что JVM не настроена на большее использование. Это можно изменить, изменив параметры запуска JVM на -Xmx1024m, чтобы получить гигабайт памяти, чтобы узнать, не исчезнет ли проблема.

Код для выполнения try catch должен выглядеть примерно так:

public static void main(String[] args) {
     try {
         MyObject o = new MyObject();
         o.process();
     } catch (Throwable t) {
         t.printStackTrace();
     }
 }

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

1 голос
/ 28 августа 2009

Одно существенное различие между двумя значениями size(long i) заключается в количестве создаваемых вами объектов.

В первой реализации Objects не создается. Во втором вы выполняете очень много автобоксинга, создавая новый Long для каждого доступа к вашему кешу и вставляя новые Long с и новые Integer с в каждой модификации.

Это объясняет увеличение использования памяти, но не отсутствие OutOfMemoryError. Увеличение кучи позволяет завершить ее для меня.

С эта Солнечная артефакт :

Производительность ... вероятно, будет низкой, так как она блокирует или распаковывает каждую операцию get или set. Это достаточно быстро для случайного использования, но было бы глупо использовать его во внутреннем цикле, критичном к производительности.

0 голосов
/ 28 августа 2009

Рекурсивный метод size (), вероятно, не подходит для кэширования. Я положил вызов cache.put (я, размер); внутри цикла for (), и он работает намного быстрее. В противном случае я также получаю ошибку OOM (больше нет места в куче).

Редактировать: вот источник - размер кэша в размерах (), но сохранение выполняется в main ().

public static void main(String[] args) {
    long num = 0;
    int  maxSize = 0;

    long start = new Date().getTime();
    for (long i = 1; i <= TARGET; i++) {
        int size = size(i);
        if (size >= maxSize) {
            maxSize = size;
            num = i;
        }
        cache.put(i, size);
    }

    long computeTime = new Date().getTime() - start;
    System.out.println(String.format("maxSize: %4d on initial starting number %6d", maxSize, num));
    System.out.println("compute time in milliseconds: " + computeTime);
}

private static int size(long i) {
    if (i == 1l) {
        return 1;
    }

    if (cache.containsKey(i)) {
        return cache.get(i);
    }

    return size(process(i)) + 1;
}

Обратите внимание, что, удаляя вызов cache.put () из size (), он не кэширует каждый вычисленный размер, но также избегает повторного кэширования ранее вычисленного размера. Это не влияет на операции с хэш-картой, но, как указывает akf, оно избегает операций автобоксирования / распаковки, из которых исходит ваша куча убийц. Я также попытался "if (! ContainsKey (i)) {cache.put () и т. Д." В size (), но, к сожалению, также не хватает памяти.

0 голосов
/ 28 августа 2009

Я получаю ошибку OutOfMemory для cache.put (i, size);

Чтобы получить ошибку, запустите вашу программу в eclipse, используя режим отладки, она появится в окне отладки. Он не создает трассировку стека в консоли.

0 голосов
/ 28 августа 2009

Видите ли вы, что после сбоя генерируется дамп кучи? Этот файл должен находиться в текущем каталоге для вашей JVM, где я хотел бы получить дополнительную информацию.

0 голосов
/ 28 августа 2009

Если ваш Java-процесс внезапно завершится сбоем, возможно, какой-то ресурс исчерпан. Как память. Вы можете попробовать установить более высокую максимальную кучу

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