JVM_09 Строковый постоянный пул StringTable

JVM

Нажмите, чтобы просмотреть полные заметки об исследовании JVM

1. Основные характеристики String

  • Строка: Строка, представленная парой кавычек "".
    • String sl = "hello";//Как определяются литералы
    • Строка s2 = новая строка («привет»);
  • Строка объявлена ​​окончательной и не может быть унаследована
  • Строка реализует интерфейс Serializable: указывает, что строка поддерживает сериализацию. Реализует интерфейс Comparable: указывает, что String можно сравнивать по размеру
  • ==String определяет final char[] в jdk8 и ранее, а значение используется для хранения строковых данных. Когда jdk9 изменяется на byte[]==
    • 结论: String再也不用char[] 来存储啦,改成了byte[] 加上编码标记,节约了一些空间。StringBuffer和StringBuilder也做了一些修改
public final class String implements java.io.Serializable, Comparable<String>,CharSequence {
@Stable
private final byte[] value;
}
  • Строка: представляет неизменяемую последовательность символов. Краткое название: Неизменяемость.
    • При переназначении строки необходимо перезаписать назначение указанной области памяти, и исходное значение не может быть использовано для присвоения.
    • При объединении существующих строк также необходимо заново выделить область памяти для присвоения, а исходное значение нельзя использовать для присвоения.
    • При вызове метода replace() объекта String для изменения указанного символа или строки также необходимо переназначить область памяти для присваивания, а исходное значение не может использоваться для присваивания.
  • Присвойте значение строке литеральным способом (отличным от нового), и строковое значение в это время объявляется в пуле строковых констант.
/**
 * String的基本使用:体现String的不可变性
 */
public class StringTest1 {
    @Test
    public void test1() {
        String s1 = "abc";//字面量定义的方式,"abc"存储在字符串常量池中
        String s2 = "abc";
        s1 = "hello";

        System.out.println(s1 == s2);//判断地址:true  --> false

        System.out.println(s1);//
        System.out.println(s2);//abc

    }

    @Test
    public void test2() {
        String s1 = "abc";
        String s2 = "abc";
        s2 += "def";
        System.out.println(s2);//abcdef
        System.out.println(s1);//abc
    }

    @Test
    public void test3() {
        String s1 = "abc";
        String s2 = s1.replace('a', 'm');
        System.out.println(s1);//abc
        System.out.println(s2);//mbc
    }
}

  • == Пул строковых констант не будет хранить строки с одинаковым содержимым. ==
    • Пул строк String представляет собой хеш-таблицу фиксированного размера с размером по умолчанию 1009. Если в StringPool слишком много строк, это вызовет серьезный конфликт хэшей, что приведет к очень длинному связанному списку, а прямое влияние длинного связанного списка заключается в том, что при вызове String.intern производительность будет значительно снижена.
    • Используйте XX: StringTableSize, чтобы установить длину StringTable.
    • StringTable исправлен в jdk6, длина которого составляет 1009, поэтому, если в константном пуле слишком много строк, эффективность будет быстро падать. Параметр StringTableSize не требуется
    • В jdk7 значение длины StringTable по умолчанию равно 60013.
    • Начиная с jdk8, 1009 — это минимальное значение, которое можно установить для длины StringTable.

2. Выделение строковой памяти

  • В языке Java существует 8 основных типов данных и специальный тип String. Эти Типы обеспечивают концепцию постоянного пула, чтобы сделать их более быстрыми и более эффективными с точки зрения использования памяти во время работы.
  • Константный пул похож на кеш, предоставляемый на системном уровне Java. Константы 8 основных типов данных Пул согласовывается системой, а постоянный пул типа String является специальным. Есть два основных способа его использования.
    • Строковые объекты, объявленные непосредственно в двойных кавычках, хранятся непосредственно в пуле констант.
      • Например: String info = "abc" ;
    • Если объект String не объявлен в двойных кавычках, вы можете использовать метод intern(), предоставленный String. Сосредоточьтесь на этом позже
  • В Java 6 и более ранних версиях пул строковых констант хранился в постоянном поколении.
  • В Java 7 инженеры Oracle внесли большое изменение в логику пула строк, то есть скорректировали расположение пула строковых констант в куче Java.
    • Все строки хранятся в куче, как и другие обычные объекты, поэтому вам нужно настроить размер кучи только при настройке приложения.
    • Концепция пула строковых констант изначально использовалась часто, но это изменение дает нам достаточно причин, чтобы пересмотреть использование String.intern() в Java 7.
  • Метапространство Java8, строковые константы в куче

Зачем нужно корректировать StringTable?
①PermSize постоянной генерации по умолчанию относительно мал;
② Частота сбора мусора в постоянном поколении низкая;

3. Основные операции со строками

class Memory {
    public static void main(String[] args) {//line 1
        int i = 1;//line 2
        Object obj = new Object();//line 3
        Memory mem = new Memory();//line 4
        mem.foo(obj);//line 5
    }//line 9

    private void foo(Object param) {//line 6
        String str = param.toString();//line 7
        System.out.println(str);
    }//line 8
}

4. Операция конкатенации строк

  • 1. Результат склейки констант и констант находится в пуле констант.Принцип оптимизации времени компиляции.
  • 2. Константы с одинаковым содержимым не будут существовать в пуле констант.
  • 3.== Пока одна из них является переменной, результат находится в куче==. Принцип склейки переменных StringBuilder
  • 4. Если результат сплайсинга вызовет метод intern(), он будет активно помещать в пул строковый объект, еще не находящийся в константном пуле, и возвращать адрес этого объекта.
 @Test
    public void test1(){
        String s1 = "a" + "b" + "c";//编译期优化:等同于"abc"
        String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2
        /*
         * 最终.java编译成.class,再执行.class
         * String s1 = "abc";
         * String s2 = "abc"
         */
        System.out.println(s1 == s2); //true
        System.out.println(s1.equals(s2)); //true
    }

    @Test
    public void test2(){
        String s1 = "javaEE";
        String s2 = "hadoop";

        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop";//编译期优化
        //如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结果:javaEEhadoop
        String s5 = s1 + "hadoop";
        String s6 = "javaEE" + s2;
        String s7 = s1 + s2;

        System.out.println(s3 == s4);//true
        System.out.println(s3 == s5);//false
        System.out.println(s3 == s6);//false
        System.out.println(s3 == s7);//false
        System.out.println(s5 == s6);//false
        System.out.println(s5 == s7);//false
        System.out.println(s6 == s7);//false
        //intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中javaEEhadoop的地址;
        //如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份javaEEhadoop,并返回次对象的地址。
        String s8 = s6.intern();
        System.out.println(s3 == s8);//true
    }

Конкатенация строк

@Test
    public void test3(){
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        /*
        如下的s1 + s2 的执行细节:(变量s是我临时定义的)
        ① StringBuilder s = new StringBuilder();
        ② s.append("a")
        ③ s.append("b")
        ④ s.toString()  --> 约等于 new String("ab")

        补充:在jdk5.0之后使用的是StringBuilder,
        在jdk5.0之前使用的是StringBuffer
         */
        String s4 = s1 + s2;//
        System.out.println(s3 == s4);//false
    }

    /*
    1. 字符串拼接操作不一定使用的是StringBuilder!
       如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
    2. 针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
     */
    @Test
    public void test4(){
        final String s1 = "a";
        final String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);//true
    }
    
    //练习:
    @Test
    public void test5(){
        String s1 = "javaEEhadoop";
        String s2 = "javaEE";
        String s3 = s2 + "hadoop";
        System.out.println(s1 == s3);//false

        final String s4 = "javaEE";//s4:常量
        String s5 = s4 + "hadoop";
        System.out.println(s1 == s5);//true

    }
    

Сравнение эффективности операции сращивания и добавления

Эффективность добавления намного выше, чем у конкатенации строк.

/*
    体会执行效率:通过StringBuilder的append()的方式添加字符串的效率要远高于使用String的字符串拼接方式!
    详情:① StringBuilder的append()的方式:自始至终中只创建过一个StringBuilder的对象
          使用String的字符串拼接方式:创建过多个StringBuilder和String的对象
         ② 使用String的字符串拼接方式:内存中由于创建了较多的StringBuilder和String的对象,内存占用更大;如果进行GC,需要花费额外的时间。

     改进的空间:在实际开发中,如果基本确定要前前后后添加的字符串长度不高于某个限定值highLevel的情况下,建议使用构造器实例化:
               StringBuilder s = new StringBuilder(highLevel);//new char[highLevel]
     */
    @Test
    public void test6(){

        long start = System.currentTimeMillis();

//        method1(100000);//4014
        method2(100000);//7

        long end = System.currentTimeMillis();

        System.out.println("花费的时间为:" + (end - start));
    }

    public void method1(int highLevel){
        String src = "";
        for(int i = 0;i < highLevel;i++){
            src = src + "a";//每次循环都会创建一个StringBuilder、String
        }
//        System.out.println(src);

    }

    public void method2(int highLevel){
        //只需要创建一个StringBuilder
        StringBuilder src = new StringBuilder();
        for (int i = 0; i < highLevel; i++) {
            src.append("a");
        }
//        System.out.println(src);
    }

5. Использование стажера()

Если объект String не объявлен с двойными кавычками, вы можете использовать внутренний метод, предоставляемый String: внутренний метод будет запрашивать, существует ли текущая строка из пула строковых констант, и если она не существует, он поместит текущую строку в постоянный пул.

  • Например: String myInfo = new String("Я люблю тебя").intern();
    То есть, если метод String.intern вызывается для любой строки, экземпляр класса, на который указывает возвращаемый результат, должен быть точно таким же, как экземпляр строки, который отображается непосредственно как константа. Следовательно, следующие выражения должны быть истинными: ("а" + "б" + "с").intern() == "abc";
    С точки зрения непрофессионала, Interned String гарантирует, что в памяти будет только одна копия строки, что может сэкономить место в памяти и ускорить выполнение задач манипулирования строками. Обратите внимание, что это значение будет сохранено в String Intern Pool.

new String("ab") создаст несколько объектов, как насчет new String("a")+new String("b")

public class StringNewTest {
    public static void main(String[] args) {
//        String str = new String("ab");

        String str = new String("a") + new String("b");
    }
}
  • Сколько объектов создаст новая строка ("ab")? Глядя на байт-код, вы знаете, что их два.
    • Объект создается новым ключевым словом в пространстве кучи
    • Другой объект: объект "ab" в пуле строковых констант. Инструкция байт-кода: ldc
  • Как насчет новой строки ("a") + новой строки ("b")?
    • Объект 1: новый StringBuilder()
    • Объект 2: новая строка («а»)
    • Объект 3: «а» в постоянном пуле
    • Объект 4: новая строка ("b")
    • Объект 5: "b" в постоянном пуле
  • Углубленный анализ: toString() в StringBuilder:
    • Объект 6: новая строка ("ab")
    • Подчеркните, что вызов toString() не генерирует "ab" в пуле строковых констант.

Вопросы на собеседовании о String.intern()

/**
 * 如何保证变量s指向的是字符串常量池中的数据呢?
 * 有两种方式:
 * 方式一: String s = "shkstart";//字面量定义的方式
 * 方式二: 调用intern()
 *         String s = new String("shkstart").intern();
 *         String s = new StringBuilder("shkstart").toString().intern();
 *
 */
public class StringIntern {
    public static void main(String[] args) {
        String s = new String("1");
        String s1 = s.intern();//调用此方法之前,字符串常量池中已经存在了"1"
        String s2 = "1";
        //s  指向堆空间"1"的内存地址
        //s1 指向字符串常量池中"1"的内存地址
        //s2 指向字符串常量池已存在的"1"的内存地址  所以 s1==s2
        System.out.println(s == s2);//jdk6:false   jdk7/8:false
        System.out.println(s1 == s2);//jdk6: true   jdk7/8:true
        System.out.println(System.identityHashCode(s));//491044090
        System.out.println(System.identityHashCode(s1));//644117698
        System.out.println(System.identityHashCode(s2));//644117698

        //s3变量记录的地址为:new String("11")
        String s3 = new String("1") + new String("1");
        //执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!

        //在字符串常量池中生成"11"。如何理解:jdk6:创建了一个新的对象"11",也就有新的地址。
        //         jdk7:此时常量中并没有创建"11",而是创建一个指向堆空间中new String("11")的地址
        s3.intern();
        //s4变量记录的地址:使用的是上一行代码代码执行时,在常量池中生成的"11"的地址
        String s4 = "11";
        System.out.println(s3 == s4);//jdk6:false  jdk7/8:true
    }

}
6 7

расширять

public class StringIntern1 {
    public static void main(String[] args) {
        //StringIntern.java中练习的拓展:
        String s3 = new String("1") + new String("1");//new String("11")
        //执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
        String s4 = "11";//在字符串常量池中生成对象"11"
        String s5 = s3.intern();
        System.out.println(s3 == s4);//false
        System.out.println(s5 == s4);//true
    }
}

Подведите итоги использования String intern()

  • В jdk1.6 попробуйте поместить этот строковый объект в пул строк.
    • ➢ Если пул строковых констант существует, он не будет помещен в него. Возвращает адрес объекта в существующем пуле строк
    • ➢ Если нет, то этот объект будет скопирован, помещен в пул строк, и будет возвращен адрес объекта в пуле строк.
  • Начиная с Jdk1.7, попробуйте поместить этот строковый объект в пул строк.
    • ➢ Если пул строковых констант существует, он не будет помещен в него. Возвращает адрес объекта в существующем пуле строк
    • ➢ Если нет, он скопирует ссылочный адрес объекта, поместит его в пул строк и вернет ссылочный адрес в пуле строк.

упражняться

Упражнение 1

public class StringExer1 {
    public static void main(String[] args) {
        //String x = "ab";
        String s = new String("a") + new String("b");//new String("ab")
        //在上一行代码执行完以后,字符串常量池中并没有"ab"

        String s2 = s.intern();//jdk6中:在串池中创建一个字符串"ab"
                               //jdk8中:串池中没有创建字符串"ab",而是创建一个引用,指向new String("ab"),将此引用返回

        System.out.println(s2 == "ab");//jdk6:true  jdk8:true
        System.out.println(s == "ab");//jdk6:false  jdk8:true
    }
}

jdk6

8

jdk7/8
9 10

Упражнение 2

public class StringExer2 {
    public static void main(String[] args) {
        String s1 = new String("ab");//执行完以后,会在字符串常量池中会生成"ab"
//        String s1 = new String("a") + new String("b");////执行完以后,不会在字符串常量池中会生成"ab"
        s1.intern();
        String s2 = "ab";
        System.out.println(s1 == s2); //false
    }
}

тест эффективности intern()

Платформы больших веб-сайтов должны хранить в памяти большое количество строк. Например, сайты социальных сетей, многие люди хранят: Пекин, район Хайдянь и другую информацию. В это время, если строки вызываются Метод intern() значительно уменьшит размер памяти.

/**
 * 使用intern()测试执行效率:空间使用上
 *
 * 结论:对于程序中大量存在存在的字符串,尤其其中存在很多重复字符串时,使用intern()可以节省内存空间。
 *
 */
public class StringIntern2 {
    static final int MAX_COUNT = 1000 * 10000;
    static final String[] arr = new String[MAX_COUNT];

    public static void main(String[] args) {
        Integer[] data = new Integer[]{1,2,3,4,5,6,7,8,9,10};

        long start = System.currentTimeMillis();
        for (int i = 0; i < MAX_COUNT; i++) {
//            arr[i] = new String(String.valueOf(data[i % data.length]));
            arr[i] = new String(String.valueOf(data[i % data.length])).intern();

        }
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为:" + (end - start));

        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.gc();
    }
}

6. Сборка мусора StrtingTable

/**
 * String的垃圾回收:
 * -Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
 *
 */
public class StringGCTest {
    public static void main(String[] args) {
//        for (int j = 0; j < 100; j++) {
//            String.valueOf(j).intern();
//        }
        //发生垃圾回收行为
        for (int j = 0; j < 100000; j++) {
            String.valueOf(j).intern();
        }
    }
}
11

7. Операция дедупликации строк в G1

  • Предыстория: Тесты на многих Java-приложениях (как больших, так и малых) дали следующие результаты:
    • ➢ Строковые объекты составляют 25% набора данных выживания кучи.
    • ➢13,5% повторяющихся объектов String в наборе данных выживания кучи
    • ➢Средняя длина объектов String составляет 45
  • Узким местом многих крупномасштабных Java-приложений является память, и тестирование показало, что в приложениях такого типа Внутри живой набор данных в куче Java состоит почти из 258 объектов String. Идя дальше, почти половина объектов String здесь являются дубликатами, что означает: строка1.равно(строка2)=истина. Существование повторяющихся строковых объектов в куче обязательно будет пустой тратой памяти. В этом проекте будет реализована автоматическая и непрерывная дедупликация повторяющихся объектов String в сборщике мусора G1, чтобы не тратить память впустую.

выполнить

  • ➢ Когда сборщик мусора работает, он будет обращаться к объектам, которые находятся в куче. Каждый посещенный объект проверяется на объект-кандидат String для дедупликации.
  • ➢ Если да, вставьте ссылку на этот объект в очередь для последующей обработки. Поток дедупликации работает в фоновом режиме, обрабатывая эту очередь. Обработка элемента очереди означает удаление элемента из очереди и последующую попытку дедупликации объекта String, на который он ссылается.
  • ➢ Используйте хеш-таблицу для записи всех уникальных массивов символов, используемых объектами String. При дедупликации хеш-таблица будет проверена, чтобы увидеть, существует ли уже идентичный массив символов в куче.
  • ➢ Если он существует, объект String будет скорректирован так, чтобы он ссылался на этот массив, освободит ссылку на исходный массив и в конечном итоге будет утилизирован сборщиком мусора.
  • ➢ Если поиск не удался, массив символов будет вставлен в хеш-таблицу, чтобы в будущем этот массив можно было использовать совместно.

параметры командной строки

  • ➢UseStringDeduplication (bool): включить дедупликацию строк.По умолчанию она отключена, и ее необходимо включить вручную.
  • ➢PrintStringDeduplicationStatistics (bool): распечатать подробную статистику дедупликации,
  • ➢StringDeduplicationAgeThreshold (uintx): строковые объекты, достигшие этого возраста, считаются кандидатами на дедупликацию.


Код обучения JVM и примечания (обновляются один за другим...)

【Код】
GitHub.com/wills звонит…
【Примечания】
Введение в JVM_01
Подсистема загрузки классов JVM_02
Область данных времени выполнения JVM_03 1- [счетчик программ + стек виртуальной машины + стек собственных методов]
Интерфейс собственного метода JVM_04
Область данных среды выполнения JVM_05 с 2 кучами
Область данных среды выполнения JVM_06 Область 3 методов
JVM_07 Область данных среды выполнения 4 — структура памяти экземпляра объекта и позиционирование доступа + прямая память
Механизм выполнения JVM_08 (механизм выполнения)
JVM_09 Строковый постоянный пул StringTable
JVM_10 Сборка мусора 1-Обзор + связанные алгоритмы
JVM_11 Сборка мусора 2 — Понятия, связанные со сборкой мусора
JVM_12 Сборка мусора 3 — сборщик мусора