Ръководство за Java String Pool

1. Общ преглед

Обектът String е най-използваният клас в езика Java.

В тази статия бързо, ние ще проучи Java String Pool - специалната региона памет където Strings се съхраняват от JVM .

2. Струнно интерниране

Благодарение на неизменността на низовете в Java, JVM може да оптимизира количеството памет, разпределена за тях, като съхранява само едно копие от всеки литерален низ в пула . Този процес се нарича интерниране .

Когато създаваме променлива String и й присвояваме стойност, JVM търси в пула String с еднаква стойност.

Ако бъде намерен, Java компилаторът просто ще върне препратка към адреса на паметта си, без да разпределя допълнителна памет.

Ако не бъде намерен, той ще бъде добавен към пула (интерниран) и неговата референция ще бъде върната.

Нека напишем малък тест, за да проверим това:

String constantString1 = "Baeldung"; String constantString2 = "Baeldung"; assertThat(constantString1) .isSameAs(constantString2);

3. Низове, разпределени с помощта на конструктора

Когато създаваме String чрез новия оператор, Java компилаторът ще създаде нов обект и ще го съхрани в пространството на купчината, запазено за JVM.

Всеки String, създаден по този начин, ще сочи към различна област на паметта със собствен адрес.

Нека да видим как това се различава от предишния случай:

String constantString = "Baeldung"; String newString = new String("Baeldung"); assertThat(constantString).isNotSameAs(newString);

4. String Literal срещу String Object

Когато създаваме String обект с помощта на оператора new () , той винаги създава нов обект в куп памет. От друга страна, ако създадем обект, използвайки синтаксис на String, напр. „Baeldung“, той може да върне съществуващ обект от String pool, ако вече съществува. В противен случай той ще създаде нов String обект и ще постави в пула от низове за бъдеща повторна употреба.

На високо ниво и двете са String обектите, но основната разлика идва от това, че операторът new () винаги създава нов String обект. Също така, когато създаваме String с помощта на литерал - той се интернира.

Това ще стане много по-ясно, когато сравним два String обекта, създадени с помощта на String literal и новия оператор:

String first = "Baeldung"; String second = "Baeldung"; System.out.println(first == second); // True

В този пример обектите String ще имат една и съща препратка.

След това нека създадем два различни обекта с помощта на new и проверим дали те имат различни препратки:

String third = new String("Baeldung"); String fourth = new String("Baeldung"); System.out.println(third == fourth); // False

По същия начин, когато сравняваме String литерал с String обект, създаден с помощта на оператор new () , използвайки оператора ==, той ще върне false:

String fifth = "Baeldung"; String sixth = new String("Baeldung"); System.out.println(fifth == sixth); // False

По принцип, когато е възможно , трябва да използваме буквалната нотация String . По-лесно е за четене и дава възможност на компилатора да оптимизира нашия код.

5. Ръчно интерниране

Можем ръчно да интернираме String в Java String Pool, като извикаме метода intern () на обекта, който искаме да интернираме.

Ръчно интерниране на String ще съхрани референцията си в пула и JVM ще върне тази референция при необходимост.

Нека създадем тестов случай за това:

String constantString = "interned Baeldung"; String newString = new String("interned Baeldung"); assertThat(constantString).isNotSameAs(newString); String internedString = newString.intern(); assertThat(constantString) .isSameAs(internedString);

6. Събиране на боклука

Преди Java 7 JVM постави Java String Pool в пространството PermGen , който има фиксиран размер - не може да бъде разширен по време на изпълнение и не отговаря на условията за събиране на боклука .

Рискът от интерниране на низове в PermGen (вместо в купчината ) е, че можем да получим грешка OutOfMemory от JVM, ако интернираме твърде много низове .

От Java 7 нататък Java String Pool се съхранява в пространството Heap , което е боклук, събран от JVM . Предимството на този подход е най -малък риск от OutOfMemory грешка , тъй като се отнасят към него Strings ще бъдат премахнати от басейна, като по този начин освобождава памет.

7. Производителност и оптимизации

В Java 6 единствената оптимизация, която можем да извършим, е увеличаване на пространството PermGen по време на извикването на програмата с опцията MaxPermSize JVM:

-XX:MaxPermSize=1G

В Java 7 имаме по-подробни опции за изследване и разширяване / намаляване на размера на пула. Нека да видим двете опции за преглед на размера на басейна:

-XX:+PrintFlagsFinal
-XX:+PrintStringTableStatistics

Ако искаме да увеличим размера на пула по отношение на групите , можем да използваме опцията StringTableSize JVM:

-XX:StringTableSize=4901

Преди Java 7u40 размерът на пула по подразбиране беше 1009 кофи, но тази стойност беше обект на няколко промени в по-новите версии на Java. За да бъдем точни, размерът на пула по подразбиране от Java 7u40 до Java 11 беше 60013 и сега се увеличи до 65536.

Имайте предвид, че увеличаването на размера на пула ще отнеме повече памет, но има предимството да намали времето, необходимо за вмъкване на низовете в таблицата.

8. Бележка за Java 9

До Java 8, низовете бяха вътрешно представени като масив от символи - char [] , кодирани в UTF-16 , така че всеки знак използва два байта памет.

С Java 9 се предоставя ново представяне, наречено Compact Strings. Този нов формат ще избере подходящото кодиране между char [] и байт [] в зависимост от съхраненото съдържание.

Тъй като новото представяне на String ще използва кодирането UTF-16 само когато е необходимо, количеството памет в купчината ще бъде значително по-малко, което от своя страна води до по-малко режийни разходи на Garbage Collector върху JVM.

9. Заключение

В това ръководство показахме как JVM и Java компилаторът оптимизират разпределението на паметта за String обекти чрез Java String Pool.

Всички примерни кодове, използвани в статията, са достъпни в GitHub.