Java Constant Pool-долгосрочная тема, а также любимый интервьюер. Есть много вопросов. Я суммирую это на этот раз.
теория
Во -первых, давайте выразим распределение виртуальной памяти JVM:
Счетчик программы - это конвейер для JVM для выполнения программы, сохраняя некоторые инструкции по прыжкам. Это слишком глубоко, и я не понимаю.
Локальный стек методов - это стек, используемый JVM для вызова методов операционной системы.
Стек виртуальных машин - это стек, используемый JVM для выполнения кода Java.
В области метода хранится некоторые константы, статические переменные, информацию класса и т. Д., Которые можно понимать как местоположение хранилища файла класса в памяти.
Куча виртуальной машины - это куча, используемая JVM для выполнения кода Java.
Постоянные бассейны в Java фактически разделены на две формы: статические постоянные бассейны и постоянные бассейны времени выполнения .
Так называемый статический постоянный пул -это постоянный пул в файле *.class. Постоянный пул в файле класса не только содержит литералы String (номер), но также содержит информацию о классах и методах, занимая большую часть пространства файла класса.
Постоянный пул времени выполнения - это виртуальная машина JVM загружает постоянный пул в файл класса в память после завершения операции загрузки класса и сохраняет его в области метода . Постоянный бассейн, который мы часто называем, относится к постоянному пулу во время выполнения в области метода.
Затем мы указываем несколько примеров постоянных пулов, которые популярны в Интернете, а затем объясняем их.
String S1 = "Hello"; String S2 = "Hello"; String S3 = "hel" + "lo"; String S4 = "hel" + new String ("lo"); String S5 = New String ("Hello"); Строка s6 = s5.intern (); Строка S7 = "H"; Строка S8 = "ello"; String S9 = S7 + S8; System.out.println (S1 == S2); // truesystem.out.println (s1 == s3); // truesystem.out.println (s1 == s4); // falsesystem.out.println (s1 == s9); // falsesystem.out.println (s4 == s5); // falsesystem.out.println (s1 == s6); // истинныйПрежде всего, в Java оператор == используется напрямую, и сравниваются эталонные адреса двух строк, а не содержимое. Пожалуйста, используйте string.equals (), чтобы сравнить содержимое.
S1 == S2 очень легко понять. Когда S1 и S2 назначены, они используют строковые литералы. Честно говоря, они напрямую пишут строку до смерти. Во время компиляции этот буквальный будет помещен непосредственно в постоянный пул файла класса, тем самым реализуя повторное использование. После загрузки постоянного пула во время выполнения, S1 и S2 указывают на один и тот же адрес памяти, они равны.
Есть яма в S1 == S3. Хотя S3 является динамически сплайсированной струной, все части, участвующие в сплайсинге, являются известными литералами. В течение периода компиляции этот сплайсинг будет оптимизирован, и компилятор напрямую поможет вам развязать его. Следовательно, строка s3 = "hel" + "lo"; оптимизирован для строки s3 = "hello"; В файле класса S1 == S3 верно.
S1 == S4, конечно, не равен. Хотя S4 также сплайсирован, новая струна («lo») не является известной буквальной, а непредсказуемой частью. Компилятор не будет оптимизировать его. Вы должны подождать до запуска, чтобы определить результат. В сочетании с теоремой инвариантности строкости вы знаете, где распределен S4, поэтому адрес должен быть другим. Краткая картина, чтобы прояснить эту идею:
S1 == S9 не равна, и причина похожа. Хотя строковые литералы, используемые S7 и S8 при назначении значений, при сплайсинге в S9, S7 и S8 оба непредсказуемы. В конце концов, компилятор является компилятором и не может использоваться в качестве интерпретатора, поэтому он не оптимизирован. Когда он запускается, новая строка, складываемая в S7 и S8, не уверена в куче и не может быть такой же, как адрес S1 в постоянном пуле области метода.
S4 == S5 больше не нужно объяснять, он определенно не одинаково, оба находятся в куче, но адреса разные.
Равенство S1 == S6 полностью связано с методом стажировки. S5 находится в куче, а контент привет. Метод стажера попытается добавить строку Hello в постоянный пул и вернуть его адрес в постоянный пул. Поскольку в постоянном пуле есть строка Hello, метод стажера непосредственно возвращает адрес; В то время как S1 уже указывает на постоянный пул в течение периода компиляции, S1 и S6 указывают на тот же адрес, который равен.
На этом этапе мы можем сделать три очень важных вывода:
Вы должны обратить внимание на поведение в течение периода компиляции, чтобы лучше понять постоянный пул.
Константы в постоянном пуле выполнения в основном поступают из постоянного пула в каждом файле класса.
Когда программа работает, JVM не будет автоматически добавлять константы в постоянный пул, если она вручную не добавит константы в постоянный пул (например, вызов методу стажировки).
Вышеуказанное только включает в себя постоянные бассейны строки. На самом деле, существуют целочисленные постоянные бассейны, постоянные бассейны с плавающей запятой и т. Д., Но они похожи, но постоянные пулы численных типов не могут быть добавлены вручную. Константы в постоянном пуле определяются, когда программа начинается. Например, постоянный диапазон в целочисленном постоянном пуле: -128 ~ 127. Только числа в этом диапазоне могут быть использованы для постоянного бассейна.
упражняться
Сказав столько теории, давайте коснемся настоящего постоянного пула.
Как упоминалось ранее, в файле класса существует статический постоянный пул. Этот постоянный пул генерируется компилятором и используется для хранения литералов в исходном файле Java (эта статья фокусируется только на литералах). Предположим, у нас есть следующий код Java:
Строка s = "hi";
Для удобства, это так просто, это верно! После составления кода в файл класса используйте WinHex, чтобы открыть файл класса двоичного формата. Как показано на картинке:
Давайте кратко объясним структуру файла класса. 4 байта в начале - это волшебное количество файла класса, которое используется для идентификации этого как файла класса. Если это прямо, это заголовок файла: Ca fe ba ba.
Следующие 4 байта представляют собой номер версии Java, а номер версии здесь 34, потому что автор составлен с JDK8, а номер версии соответствует уровню версии JDK. Более высокая версия может быть совместима с нижней версией, но нижняя версия не может выполнить более высокую версию. Таким образом, если однажды читатели хотят знать, с какой сборной JDK File Class Class составлен, вы можете посмотреть на эти 4 байта.
Следующим является постоянный вход в бассейн. Количество постоянных постоянных бассейнов идентифицируется 2 байтами у входа. В этом примере значение составляет 00 1а. Он переведен в десятичное значение и составляет 26, что означает, что есть 25 констант. 0 -я константа является специальным значением, поэтому есть только 25 констант.
Постоянный бассейн хранит различные типы констант. Все они имеют свои собственные типы и собственные спецификации хранения. Эта статья фокусируется только на константах струн. Константы строки начинаются с 01 (1 байт), а затем записывают длину строки с 2 байтами, а затем фактическое содержание строки. В этом случае это: 01 00 02 68 69.
Далее, давайте поговорим о постоянном бассейне. Поскольку пул постоянного времени выполнения находится в области метода, мы можем установить размер области метода через параметры JVM: -xx: permsize, -xx: maxpermsize, тем самым косвенно ограничивая постоянный размер пула.
Предположим, что параметр запуска JVM: -xx: permsize = 2m -xx: maxpermsize = 2m, а затем запустите следующий код:
// Сохраняйте ссылки на предотвращение автоматического списка сбора мусора <string> list = new ArrayList <string> (); int i = 0; while (true) {// вручную добавлять постоянный список.Программа сразу же бросит: исключение в Thread "Main" java.lang.outofmemoryerror: Empless Space Exception. Пространство пермина - это область метода, которая достаточно, чтобы указать, что постоянный пул находится в области метода.
В JDK8 область метода была удалена, а область Metaspace была заменена. Поэтому нам нужно использовать новый параметр JVM: -xx: maxmetaspacesize = 2m и все еще запустить приведенный выше код, бросание: java.lang.outofmemoryerror: metaspace исключение. Точно так же объясняется, что бассейн постоянного времени выполнения разделен на область Metaspace. Для конкретных знаний о области Metaspace, пожалуйста, найдите это самостоятельно.
Все коды в этой статье были протестированы и переданы в рамках JDK7 и JDK8. Другие версии JDK могут иметь небольшие различия. Пожалуйста, изучите это сами.