Пакетное использование памяти в Java

Что такое NullPointerException?

Хорошим местом для начала является JavaDocs . Они охватывают это:

Брошено, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:

  • Вызов метода экземпляра нулевого объекта.
  • Доступ или изменение поля нулевого объекта.
  • Выполнение длины null, как если бы это был массив.
  • Доступ или изменение слотов с нулевым значением, как если бы это был массив.
  • Бросать нуль, как если бы это было значение Throwable.

Приложения должны бросать экземпляры этого класса для указания других незаконных видов использования нулевого объекта.

Также, если вы попытаетесь использовать нулевую ссылку с synchronized, который также выдаст это исключение, за JLS :

SynchronizedStatement:
    synchronized ( Expression ) Block
  • В противном случае, если значение выражения равно null, NullPointerException.

Как это исправить?

Итак, у вас есть NullPointerException. Как вы это исправите? Возьмем простой пример, который выдает NullPointerException:

public class Printer {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer();
        printer.print();
    }
}

Идентифицирует нулевые значения

. Первый шаг - точно определить , значения которого вызывают исключение . Для этого нам нужно выполнить некоторую отладку. Важно научиться читать stacktrace . Это покажет вам, где было выбрано исключение:

Exception in thread "main" java.lang.NullPointerException
    at Printer.printString(Printer.java:13)
    at Printer.print(Printer.java:9)
    at Printer.main(Printer.java:19)

Здесь мы видим, что исключение выбрано в строке 13 (в методе printString). Посмотрите на строку и проверьте, какие значения равны нулю, добавив протоколирующие операторы или используя отладчик . Мы обнаруживаем, что s имеет значение null, а вызов метода length на него вызывает исключение. Мы видим, что программа прекращает бросать исключение, когда s.length() удаляется из метода.

Трассировка, где эти значения взяты из

Затем проверьте, откуда это значение. Следуя вызовам метода, мы видим, что s передается с printString(name) в методе print(), а this.name - null.

Трассировка, где эти значения должны быть установлены

Где установлен this.name? В методе setName(String). С некоторой дополнительной отладкой мы видим, что этот метод вообще не вызывается. Если этот метод был вызван, обязательно проверьте порядок , что эти методы вызывают, а метод set не будет называться после методом печати. ​​

Этого достаточно, чтобы дать нам решение: добавить вызов printer.setName() перед вызовом printer.print().

Другие исправления

Переменная может иметь значение по умолчанию setName может помешать ему установить значение null):

private String name = "";

Либо метод print, либо printString может проверить значение null например:

printString((name == null) ? "" : name);

Или вы можете создать класс, чтобы name всегда имел ненулевое значение :

public class Printer {
    private final String name;

    public Printer(String name) {
        this.name = Objects.requireNonNull(name);
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer("123");
        printer.print();
    }
}

См. также:

Я все еще не могу найти проблему

Если вы попытались отладить проблему и до сих пор не имеете решения, вы можете отправить вопрос для получения дополнительной справки, но не забудьте включить то, что вы пробовали до сих пор. Как минимум, включите stacktrace в вопрос и отметьте важные номера строк в коде. Также попробуйте сначала упростить код (см. SSCCE ).

25
задан Benjamin 31 January 2014 в 19:05
поделиться

2 ответа

Когда программа завершает работу, уменьшается ли использование памяти в диспетчере задач в Windows? Я думаю, что память освобождается, но не отображается как освобожденная мониторами задач по умолчанию в ОС, которую вы отслеживаете. Пройдите этот вопрос по C ++ Проблема с освобождением вектора указателей

2
ответ дан Community 31 January 2014 в 19:05
поделиться

... но мне всегда кажется, что как только Java коснется некоторой памяти, она исчезнет навсегда. Вы никогда не получите его обратно.

Это зависит от того, что вы подразумеваете под «навсегда».

Я также слышал, что некоторые JVM действительно возвращают память ОС, когда они готовы и способны. К сожалению, учитывая то, как обычно работают низкоуровневые API памяти, JVM должна отдавать целые сегменты, и, как правило, сложно «эвакуировать» сегмент, чтобы его можно было вернуть.

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

В этом случае вы должны убедиться, что ваша максимальная память никогда не бывает слишком высокой, иначе ваше приложение будет постоянно поглощать сотни МБ ОЗУ.

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

(Комментарии ОП ниже указывают, что это не то, что он пытался сказать. Тем не менее, это то, что он сказал.)


Что касается эффективности сбора мусора, мы можем смоделируйте стоимость запуска эффективного сборщика мусора следующим образом:

cost ~= (amount_of_live_data * W1) + (amount_of_garbage * W2)

где W1 и W2 - (мы предполагаем) константы, которые зависят от коллектора. (На самом деле, это чрезмерное упрощение. Первая часть не является линейной функцией от числа живых объектов. Однако я утверждаю, что это не имеет значения для следующего.)

Эффективность коллектора может быть затем сформулирована как:

efficiency = cost / amount_of_garbage_collected

, которая (если мы предположим, что GC собирает все данные) увеличивается до

efficiency ~= (amount_of_live_data * W1) / amount_of_garbage + W2.

Когда GC работает,

heap_size ~= amount_of_live_data + amount_of_garbage

так

efficiency ~= W1 * (amount_of_live_data / (heap_size - amount_of_live_data) )
              + W2.

Другими словами:

  • при увеличении размера кучи эффективность стремится к константе (W2), но
  • вам нужно большое отношение heap_size к amount_of_live_data, чтобы это произошло.

Другой момент заключается в том, что для эффективного копирующего сборщика W2 покрывает только стоимость обнуления пространства, занимаемого мусорными объектами, в «из космоса». Остальное (отслеживание, копирование живых объектов в «космос» и обнуление «из космоса», которое они занимали) является частью первого слагаемого исходного уравнения, т. Е. Покрывается W1. Это означает, что W2, вероятно, быть значительно меньше, чем W1 ... и что первый член окончательного уравнения значим дольше.

Теперь, очевидно, это теоретический анализ, а модель затрат - это упрощение того, как на самом деле сборщики мусора работать (и это не учитывает «реальную» работу, которую выполняет приложение, или эффекты системного уровня, связывающие слишком много памяти.) Однако математика говорит мне, что с точки зрения Эффективность GC , большая куча действительно очень помогает .

5
ответ дан Stephen C 31 January 2014 в 19:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: