«Это первый день моего участия в ноябрьском испытании обновлений, ознакомьтесь с подробностями события:Вызов последнего обновления 2021 г.".
Выделение и управление памятью Java является одной из основных технологий Java. Ранее мы представили управление памятью Java и утечки памяти, а также знания о сборке мусора Java. Сегодня мы снова углубимся в ядро Java и представим знания Java о распределении памяти в деталь. . Как правило, в распределении памяти Java задействованы следующие области:
◆Регистрация: у нас нет контроля в программе
◆Стек: Хранит базовые типы данных и ссылки на объекты, но сами объекты хранятся не в стеке, а в куче
◆Куча: хранит данные, сгенерированные новыми
◆Статический домен: статические элементы, хранящиеся в объекте, определяемом статическим
◆Константный пул: хранить константы
◆Хранение без оперативной памяти: постоянное место для хранения, например, жесткий диск
Стек в распределении памяти Java
Некоторые основные типы переменных данных и переменных ссылок на объекты, определенные в функции, размещаются в памяти стека функции.
Когда переменная определена в блоке кода, Java выделяет место в памяти для переменной в стеке.Когда переменная выходит из области видимости, Java автоматически освобождает пространство памяти, выделенное для переменной, и пространство памяти может быть немедленно использовано для других целей. целей.
Куча в распределении памяти Java
Куча памяти используется для хранения объектов и массивов, созданных с помощью new. Память, выделенная в куче, управляется автоматическим сборщиком мусора виртуальной машины Java.
После того, как массив или объект сгенерирован в куче, вы также можете определить специальную переменную в стеке, чтобы значение переменной в стеке было равно первому адресу массива или объекта в памяти кучи, а переменная в стеке становится массивом или переменной ссылки на объект. Ссылочная переменная эквивалентна имени массива или объекта, и вы можете использовать ссылочную переменную в стеке для доступа к массиву или объекту в куче позже в программе. Ссылочная переменная похожа на имя массива или объекта.
Ссылочные переменные — это обычные переменные, размещаемые в стеке при их определении и освобождаемые, когда программа выходит за пределы области видимости. Сами массивы и объекты размещаются в куче, даже если программа запускается за пределами блока кода, где находится оператор, использующий new для генерации массивов или объектов, память, занимаемая самими массивами и объектами, не освобождается, а сами массивы и объекты не имеют ссылочных переменных, чтобы указать на него.Когда он становится мусором, его нельзя использовать, но он по-прежнему занимает место в памяти и собирается (освобождается) сборщиком мусора в неопределенное время. Это также является причиной того, что Java занимает больше памяти.
На самом деле переменные в стеке указывают на переменные в куче памяти, которые в Java являются указателями!
постоянный пул
Константный пул относится к некоторым данным, которые определяются во время компиляции и сохраняются в скомпилированном файле .class. В дополнение к постоянным значениям (final), содержащим различные базовые типы (такие как int, long и т. д.) и типы объектов (такие как String и array), определенные в коде, он также содержит некоторые символические ссылки в текстовом виде, Такие как:
◆ Полные имена классов и интерфейсов;
◆Имя и дескриптор поля;
◆ Методы, имена и дескрипторы.
Виртуальная машина должна поддерживать постоянный пул для каждого загруженного типа. Пул констант — это упорядоченный набор констант, используемых типом, включая прямые константы (строковые, целочисленные константы и константы с плавающей запятой) и символические ссылки на другие типы, поля и методы.
Для строковых констант его значение находится в пуле констант. Пул констант в JVM существует в виде таблицы в памяти.Для типа String существует таблица фиксированной длины CONSTANT_String_info, используемая для хранения значений текстовых строк.Примечание: в этой таблице хранятся только значения текстовых строк, а не символы. Цитировать. При этом должно быть более четкое понимание места хранения строковых значений в пуле констант.
Во время выполнения программы пул констант хранится в области методов, а не в куче.
куча и стек
Куча Java — это область данных времени выполнения, из которой классы (объекты) выделяют пространство.Эти объекты создаются такими инструкциями, как new, newarray, anewarray и multianewarray, и их не нужно явно освобождать программным кодом.Куча отвечает Да, преимущество кучи в том, что размер памяти можно выделить динамически, а время жизни не нужно сообщать компилятору заранее, потому что она выделяет память динамически во время выполнения, а сборщик мусора Java будет автоматически собирать эти данные, которые больше не используются.Но недостатком является то, что скорость доступа ниже из-за динамического выделения памяти во время выполнения.
Преимущество стека в том, что скорость доступа выше, чем у кучи, уступает только регистру, а данные стека можно использовать совместно. Но недостатком является то, что размер и время жизни данных, хранящихся в стеке, должны быть детерминированными, что не обеспечивает гибкости. В стеке в основном хранятся некоторые основные типы переменных данных (int, short, long, byte, float, double, boolean, char) и дескрипторы объектов (ссылки).
У стека есть очень важная особенность, то есть данные, хранящиеся в стеке, могут использоваться совместно. Предположим, мы также определяем:
int a = 3; int b = 3; компилятор сначала обрабатывает int a = 3; сначала он создаст в стеке ссылку на переменную a, а затем проверит, есть ли в стеке значение 3, если нет, он 3 сохранит его, затем укажет на 3. Затем обработайте int b = 3; после создания ссылочной переменной b, поскольку в стеке уже есть значение 3, она укажет b прямо на 3. Таким образом, a и b одновременно указывают на 3.
В это время, если снова установлено a=4, то компилятор повторно проверит, есть ли в стеке значение 4, если нет, он сохранит 4 и сделает точку на 4; если оно уже существует, напрямую указать на этот адрес. Следовательно, изменение значения a не повлияет на значение b.
Следует отметить, что такое совместное использование данных отличается от совместного использования двух ссылок на объекты, указывающих на один объект одновременно, потому что в этом случае модификация a не влияет на b, это делается компилятором, это выгодно сэкономить место. Переменная ссылки на объект изменяет внутреннее состояние объекта, которое влияет на другую переменную ссылки на объект.
Строка представляет собой специальную оболочку данных класса. Можно использовать:
String str = new String("abc"); String str = "abc"; Есть две формы для создания, первая - использовать new() для создания нового объекта, который будет храниться в куче. При каждом вызове создается новый объект. Второй — сначала создать переменную ссылки на объект str класса String в стеке, а затем перейти к пулу строковых констант, чтобы узнать, есть ли «abc» в пуле строковых констант через символическую ссылку. Если нет, сохранить « abc" в строковой константе. pool, и сделайте так, чтобы str указывал на "abc", если уже есть "abc", прямо сделайте так, чтобы str указывал на "abc".
Используйте метод equals(), чтобы сравнить, равны ли значения в классе; используйте == при проверке, указывают ли ссылки двух классов-оболочек на один и тот же объект.Следующие примеры иллюстрируют вышеизложенную теорию.
String str1 = "abc";String str2 = "abc";System.out.println(str1==str2);//true Видно, что str1 и str2 указывают на один и тот же объект.
String str1 =new String ("abc"); String str2 =new String ("abc"); System.out.println(str1==str2); // false Использование new означает создание различных объектов. Генерируйте по одному.
Таким образом, используя второй метод для создания нескольких строк «abc», в памяти фактически находится только один объект.Этот метод записи выгоден и экономит место в памяти.В то же время он может повысить скорость работы программы до определенной степени, потому что JVM автоматически решит, необходимо ли создавать новый объект в соответствии с фактическим положением данных в стеке. Что касается кода String str = new String("abc");, новый объект создается в куче независимо от того, равно его строковое значение или нет, необходимо ли создавать новый объект, что увеличивает нагрузку программы.
С другой стороны, будьте осторожны: когда мы определяем класс в таком формате, как String str = "abc";, мы всегда предполагаем, что создается объект str класса String. Не беспокойтесь о ловушке! Возможно, объект не был создан! Скорее, он может просто указывать на объект, который был создан ранее. Только с помощью метода new() можно каждый раз создавать новый объект.
Из-за неизменяемости класса String, когда переменная String должна часто изменять свое значение, следует рассмотреть возможность использования класса StringBuffer для повышения эффективности программы.
\
- Прежде всего, String не принадлежит к 8 основным типам данных, String является объектом. Поскольку значение по умолчанию для объекта равно null, значение по умолчанию для String также равно null, но это особый тип объекта с некоторыми функциями, которых нет у других объектов. \
2. И new String(), и new String("") объявляют новую пустую строку, которая является пустой строкой, а не нулевой;
3. Разница между String str="kvill" и String str=new String ("kvill")
Пример:
String s0="kvill";
String s1="kvill";
String s2="kv" + "ill";
System.out.println( s0==s1 );
System.out.println( s0==s2 );
Результат:
true
true
Во-первых, нам нужно знать, что в результате Java гарантирует наличие только одной копии строковой константы.
Поскольку «kvill» в s0 и s1 в этом примере являются строковыми константами, они определяются во время компиляции, поэтому s0==s1 истинно; а «kv» и «ill» также являются строковыми константами. конкатенация нескольких строковых констант, она сама должна быть строковой константой, поэтому s2 также анализируется как строковая константа во время компиляции, поэтому s2 также является «kvill» в пуле констант. Итак, мы получаем, что s0==s1==s2; строки, созданные с помощью new String(), не являются константами и не могут быть определены во время компиляции, поэтому строки, созданные с помощью new String(), не помещаются в пул констант, они имеют собственное адресное пространство.
Пример:
String s0="kvill";
String s1=new String("kvill");
String s2="kv" + new String("ill");
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );
Результат:
false
false
false
В примере 2 s0 по-прежнему является приложением "kvill" в пуле констант. Поскольку s1 нельзя определить во время компиляции, это ссылка на новый объект "kvill", созданный во время выполнения. Поскольку s2 имеет вторую половину нового String("ill"), поэтому его нельзя определить во время компиляции, так что это тоже приложение вновь созданного объекта "kvill"; разберитесь в этом и узнайте, почему получается такой результат.
4. String.intern():
Еще одно добавление: пул констант, существующий в файле .class, загружается JVM во время выполнения и может быть расширен. Метод intern() для String — это способ расширить пул констант; когда экземпляр String str вызывает метод intern(), Java проверяет, есть ли в пуле констант строковая константа с тем же Unicode, и если да, возвращает его ссылка, если нет, добавить строку с Unicode, равную str, в пул констант и вернуть ее ссылку; см. пример, чтобы было понятно
Пример:
String s0= "kvill";
String s1=new String("kvill");
String s2=new String("kvill");
System.out.println( s0==s1 );
System.out.println( "**********" );
s1.intern();
s2=s2.intern(); //把常量池中"kvill"的引用赋给s2
System.out.println( s0==s1);
System.out.println( s0==s1.intern() );
System.out.println( s0==s2 );
Результат:
false
false //Хотя s1.intern() выполняется, возвращаемое значение не присваивается s1
true //Указывает, что s1.intern() возвращает ссылку на "kvill" в пуле констант
true
Наконец, я развеиваю недоразумение: кто-то сказал: «Используйте метод String.intern() для сохранения класса String в глобальной таблице String, если строка Unicode с тем же значением уже есть в этой таблице, тогда этот метод возвращает адрес существующая строка в таблице. Если в таблице нет строки с таким же значением, она зарегистрирует свой собственный адрес в таблице». Если я понимаю глобальную таблицу строк, которую он сказал как постоянный пул, Его последнее предложение: « Если в таблице нет строки с таким же значением, пропишите свой адрес в таблице" неверно:
Пример:
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() );
System.out.println( s1+" "+s2 );
System.out.println( s2==s1.intern() );
результат:
false
kvill kvill
true
В этом классе мы не объявляли константу kvill, поэтому сначала в пуле констант не было kvill, а когда мы вызывали s1.intern(), в пул констант добавлялась новая константа kvill. Оригинальный "квилл", которого нет в пуле констант, все же существует, то есть не "прописывает свой адрес в пуле констант".
s1==s1.intern() имеет значение false, указывая на то, что исходный "kvill" все еще существует; s2 теперь является адресом "kvill" в пуле констант, поэтому s2==s1.intern() имеет значение true.
5. О equals() и ==:
Для String это означает просто сравнить, эквивалентны ли последовательности Unicode двух строк, и вернуть true, если они равны; а == — сравнить, совпадают ли адреса двух строк, то есть являются ли они ссылками. к той же строке.
6. О том, что строка неизменяема
Об этом можно многое сказать, если вы знаете, что экземпляр String не изменится после его создания, например: String str="kv"+"ill"+" "+"ans"; то есть , есть 4 строки Константы, сначала "kv" и "ill" генерируют "kvill" в памяти, затем "kvill" и " " генерируют "kvill" в памяти, и наконец генерируют "kvill ans"; и помещаем эту строку Адрес строки присваивается str, потому что «неизменяемость» String создает много временных переменных, поэтому рекомендуется использовать StringBuffer, поскольку StringBuffer можно изменить.
Вот некоторые часто задаваемые вопросы о String:
Окончательное использование и понимание в String
final StringBuffer a = new StringBuffer("111");
final StringBuffer b = new StringBuffer("222");
a=b;//此句编译不通过
final StringBuffer a = new StringBuffer("111");
a.append("222");// 编译通过
Можно видеть, что final действителен только для "значения" (то есть адреса памяти) ссылки. Это заставляет ссылку указывать только на объект, на который изначально указывалось, и изменение ее точки вызовет время компиляции. ошибка. Что касается изменения объекта, на который он указывает, final не несет ответственности.
Несколько примеров проблем с пулом констант String
Ниже приводится сравнительный анализ и понимание нескольких распространенных примеров:
String a = "a1";
String b = "a" + 1;
System.out.println((a == b)); //result = true
String a = "atrue";
String b = "a" + "true";
System.out.println((a == b)); //result = true
String a = "a3.4";
String b = "a" + 3.4;
System.out.println((a == b)); //result = true
Анализ: JVM оптимизирует соединение "+" константной строки со значением после соединения при компиляции программы. Например "а"+1 оптимизируется компилятором. После этого в классе уже а1. Значение его строковой константы определяется во время компиляции, поэтому конечный результат приведенной выше программы полностью истинен.
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false
Анализ: Для ссылок на строки JVM есть ссылки на строки в "+" соединении строк, и значение ссылки не может быть определено при компиляции программы, то есть "a" + bb не может быть оптимизировано компилятором, Только во время выполнения программы для динамического выделения и назначения нового адреса после подключения к b. Таким образом, результат вышеуказанной программы также является ложным.
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true
Анализ: единственное отличие от [3] заключается в том, что строка bb является финальной модификацией.Для финальных модифицированных переменных она анализируется во время компиляции как локальная копия константного значения, хранящегося в собственном пуле констант или встроенного в свой собственный пул констант. .в потоке байт-кода. Итак, в это время «а» + «бб» и «а» + «б» имеют одинаковый эффект. Таким образом, результат вышеприведенной программы верен.
String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println((a == b)); //result = false
private static String getBB() { return "b"; }
Анализ: для ссылки на строку JVM bb ее значение не может быть определено во время компиляции.Только после вызова метода во время выполнения программы возвращаемое значение метода динамически связывается с «a», а адрес присваивается b, поэтому результат вышеуказанной программы является ложным.
Из приведенных выше 4 примеров можно сделать вывод, что:
String s = "a" + "b" + "c";
эквивалентно String s = "abc";
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c;
Это другое, конечный результат равен:
StringBuffer temp = new StringBuffer();
temp.append(a).append(b).append(c);
String s = temp.toString();
Из приведенных выше результатов анализа нетрудно сделать вывод, что String использует оператор соединения (+) для анализа неэффективности причины, которая выглядит следующим образом:
public class Test {
public static void main(String args[]) {
String s = null;
for(int i = 0; i < 100; i++) {
s += "a";
}
}
}
Каждый раз, когда выполняется +, создается объект StringBuilder, а затем он отбрасывается после добавления. В следующий раз, когда приходит цикл, объект StringBuilder создается заново, а затем добавляется строка и так далее до конца. Если мы напрямую добавляем объекты StringBuilder, мы можем сэкономить N - 1 время создания и уничтожения объектов. Поэтому для приложений, которым необходимо соединять строки в цикле, объекты StringBuffer или StringBulider обычно используются для операций добавления.
Понимание и анализ внутреннего метода объектов String:
public class Test4 {
private static String a = "ab";
public static void main(String[] args){
String s1 = "a"; String s2 = "b";
String s = s1 + s2;
System.out.println(s == a);//false
System.out.println(s.intern() == a);//true
}
}
Использование здесь Java — постоянная проблема пула. Для операции s1+s2 новый объект фактически воссоздается в куче, а s сохраняет содержимое нового объекта в пространстве кучи, поэтому значения s и a не равны. При вызове метода s.intern() он может вернуть значение адреса s в пуле констант, поскольку значение a хранится в пуле констант, поэтому значения s.intern и a равны.
Суммировать
Стек используется для хранения данных локальных переменных некоторых примитивных типов данных и ссылок на объекты (строки, массивы, объекты и т. д.), но не хранит содержимое объекта. Объекты, созданные с использованием нового ключевого слова, хранятся в куче.
String — это специальный класс-оболочка, его ссылка хранится в стеке, а содержимое объекта должно определяться в соответствии с методом создания (константный пул и куча).Некоторые уже созданы во время компиляции и хранятся в строковом константном пуле , а некоторые создаются во время выполнения. Используйте новое ключевое слово и сохраните их в куче.