(二)JVM-内存结构

155 阅读15分钟

一、内存结构的组成

20230828-152331.jpg

二、程序计数器(PC Register)

定义

Program Counter Regiter 程序计数器(寄存器)

作用

程序计数器:记住下一条JVM指令的执行地址。如下图所示,0,3,5,8 就是JVM指令在内存中的地址,程序计数器记录的就是这些指令的执行地址。

程序计数器是由寄存器组成的。

20230828-154546.jpg

特点
  • 程序计数器是线程私有的。
  • Java虚拟机规范规定,程序计数器不会存在内存溢出问题。

三、虚拟机栈(JVM Stacks)

3.1 定义

Java Virtual Machine Stacks Java虚拟机栈

  • 虚拟机栈:提供了每个线程运行时需要的内存空间。
  • 栈帧:每次方法运行时所占用的内存。如:参数,局部变量,返回地址。
  • 一个虚拟机栈由多个栈帧(Frame)组成。
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法,就就是在栈顶部的栈帧。
  • 栈帧以先进后出的顺序进出虚拟机栈。

20230828-161428.jpg

20230828-162003.jpg

如上图代码所示,方法调用链是main——> method1——> method2。

所以,入栈顺序依次是 main,method1,method2。

执行完的方法先出栈,所以出栈顺序依次是method2,method1,main。

问题辨析
  1. 垃圾回收是否涉及栈内存?

    答:不涉及。因为栈帧在每次方法调用结束后会自动弹出栈,也就是会自动被回收掉,所以不需要垃圾回收。

  2. 栈内存分配越大越好吗?

    答:使用-Xss命令设置栈内存的大小。栈内存越大,线程数越少。因为物理内存的大小是一定的,栈内存越大,可同时运行的线程数就越少。比如:物理内存总共有500M,栈内存是1M,这样可以有500个线程同时运行;但如果栈内存扩大到5M,只有100个线程可以同时运行。

    一般采用系统默认的栈内存大小。Linux系统默认是1024KB。

  3. 方法内的局部变量是否线程安全?

    • 如果方法内的局部变量没有逃离方法的作用范围,它是线程安全的。因为局部变量记录在每个栈帧内而且栈帧是线程私有的,所以每个线程都会单独持有一份局部变量。
    • 如果局部变量引用了对象,并逃离了方法的作用范围,就需要考虑线程安全。
class Pool {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append(4);
        sb.append(5);
        sb.append(6);
        
        new Thread(() ->{m2(sb);}).start();
    }

    /**
     * 局部变量sb是线程安全的
     */
    public void m1() {
        StringBuilder sb = new StringBuilder();
        sb.append(1);
        sb.append(2);
        sb.append(3);
        
        System.out.println(sb.toString());
    }

    /**
     * 入参sb是不线程安全的,可能有多个线程同时对它做修改。比如main方法中的例子。
     */
    public static void m2(StringBuilder sb) {
        sb.append(1);
        sb.append(2);
        sb.append(3);

        System.out.println(sb.toString());
    }

    /**
     * 变量sb不是线程安全的,因为可以有多个线程同时使用它的引用并修改它。
     * @return
     */
    public StringBuilder m3() {
        StringBuilder sb = new StringBuilder();
        sb.append(1);
        sb.append(2);
        sb.append(3);

        return sb;
    }
}

3.2 栈内存溢出

1. 什么情况下导致栈内存溢出StackOverFlowError?
  • 栈帧过多导致栈内存溢出。比如:方法的递归调用,多个类之间的循环引用。
  • 栈帧过大导致栈内存溢出。

例如下面这段代码中,Dept类和Emp类相互引用,objectMapper.writeValueAsString(dept) 时发生了java.lang.StackOverFlowError。

class Pool {
    public static void main(String[] args) throws JsonProcessingException {
        Dept dept = new Dept();
        dept.setName("market");
        
        List<Emp> emps = new ArrayList<>(2);
        for (int i = 0; i < 2; i++) {
            Emp emp = new Emp();
            emp.setName("emp" + i);
            emp.setDept(dept);

            emps.add(emp);
        }
        dept.setEmps(emps);
        ObjectMapper objectMapper = new ObjectMapper();
        System.out.println(objectMapper.writeValueAsString(dept));
    }
}

/**
 * 该类中引用了Emp类
 */
class Dept {
    private String name;
    
    private List<Emp> emps;

    public String getName() {
        return name;
    }

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

    public List<Emp> getEmps() {
        return emps;
    }

    public void setEmps(List<Emp> emps) {
        this.emps = emps;
    }
}

/**
 * 该类中引用了Dept类
 */
class Emp {
    
    private String name;
    
    private Dept dept;

    public String getName() {
        return name;
    }

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

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }
}

3.3 线程运行诊断

案例 1:CPU占用过多
  1. top 命令定位哪个进程对CPU的占用过高。

如下图所示,PID=32655的进程占用了99.8%的CPU。

1693231222790.jpg 2. 用ps H -eo pid,tid,%cpu | grep pid(具体的进程id)命令进一步定位是哪个线程引起的cpu占用过高。

如下图所示,PID=32655,TID=32665的线程占用了99.5%的CPU。

1693231377847.jpg

  1. jstack pid 命令 可以看到进程PID下的所有线程,根据TID找到具体的线程,进一步定位到问题代码的源码行行号。

如下图所示,使用jstack命令后,可以看到PID=32655下的所有线程,然后根据TID=32665找到nid=0x7f99的线程,就是这个线程占用了99.5%的CPU。该线程的状态是RUNNABLE的,定位到有问题的代码是类Demo_16中的第8行。

1693231688908.jpg

1693231745050.jpg

案例 2:程序运行很长时间没有结果
  1. 使用 jstack命令 定位到线程死锁问题。

1693232328088.jpg

四、本地方法栈(Native Method Stacks)

本地方法栈给本地方法native method的运行提供内存空间。

五、堆(Heap)

5.1 定义

通过new关键字创建对象都会使用堆内存。

特点
  • 它是线程共享的,堆中对象都需要考虑线程安全问题。
  • 有垃圾回收机制。

5.2 堆内存溢出OutOfMemoryError: Java Heap Space

使用**命令-Xmx **设置堆内存的大小

5.3 堆内存诊断

1. jps 工具

查看当前系统中有哪些Java进程。

1693236610899.jpg

2. jmap 工具

使用jmap -heap PID命令查看某一个时刻堆内存使用情况。

1693236671845.jpg

1693236867259.jpg

1693237012923.jpg

1693237080462.jpg

3. jconsole 工具

图形界面的,多功能的监测工具,可以连续监测。查看堆内存使用量、线程数量、类数量、CPU使用率、监测死锁,执行GC等功能。

4. jvisualvm 工具

图形界面的工具,比jconsole还要强大,除了具备jconsole工具的功能外,还有一个堆Dump功能,可以抓取某一时刻堆内存的快照,并查看占用堆内存最高的对象是哪个。

1693237987584.jpg

1693237941536.jpg

1693238065976.jpg

1693238417708.jpg

六、方法区(Method Area)

6.1 定义

  • 方法区是线程共享的。
  • 在虚拟器启动时被创建。
  • 方法区在逻辑上是堆的组成部分。
  • 方法区会产生内存溢出。

6.2 组成

20230830-092858.jpg

  • ClasslLoader: 可以用来加载类的二进制字节码

6.3 方法区内存溢出

  • 1.8以前会导致永久代内存溢出 OutOfMemoryError: PermGen space
    • XX: MaxPermSize=8m
  • 1.8以后会导致元空间内存溢出 OutOfMemoryError: Metaspace
    • 默认情况下,元空间没有设置大小上限。 -XX:MaxMetaspaceSize=8m,通过这个命令可以设置元空间的最大内存空间。

6.3 运行时常量池

  • 常量池:使用javap -v 命令得到的一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息。

20230830-142337.jpg

  • 运行时常量池:常量池是存在于*.class文件中的,当该类被加载时,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址。

6.4 StringTable(串池)的特性

StringTable是hashtable结构的,并且不能扩容。

JDK1.6时,StringTable是放在永久代中,只有在放生FullGC的时候,才会去永久代中进行垃圾回收,发生频率低。但是StringTable中存放的都是程序运行过程中产生的字符串,会有大量无用对象得不到及时回收。

为了能够及时回收,JDK1.8时,将StringTable是放在了堆中。

//常量池中的信息,都会被加载到运行时常量池中,这时a b ab 都是常量池中的符号,还没有变成 Java字符串对象。
// ldc #2 会把a符号变为"a"字符串对象。

public static void main(String[] args) {
    String s1 = "a";
    String s2 = "b";
    String s3 = "a" + "b";  //javac 在编译期间优化,因为"a","b"都是确定的常量,所以结果已经在编译期间确定为"ab"。
    
    String s4 = s1 + s2;
    String s5 = "ab";
    String s6 = s4.intern();  //将s4对象的字符串值尝试放入串池,如果有则不会放入,如果没有则放入串池,并把串池中的字符串返回。也就是说,s6指向串池中的字符串。

    //问题如下
    System.out.println(s3 == s4);
    System.out.println(s3 == s5);
    System.out.println(s3 == s6);

    String x2 = new String("c") + new String("d");
    String x1 = "cd";
    x2.intern();

    //问:如果调换了最后两行代码的位置呢?如果是jdk1.6呢?
    System.out.println(x1 == x2);
}

使用javap -v DemoApplication.class命令反编译,得到下面的内容,分为几个大类:

  1. 类的基本信息,包括最近修改时间、MD5校验码、源文件名称、JDK版本、父类、接口等信息。
  2. 常量池。
  3. 类的方法及执行指令,具体包括:
    • 所有方法及执行指令。
    • LineNumberTable 行号表。
    • LocalVariableTable 本地变量表。
    • StackMapTable。
  4. InnerClasses。
  5. BootstrapMethods。
D:\IdeaProjects\demo\target\classes\com\example\demo>javap -v DemoApplication.class
Classfile /D:/IdeaProjects/demo/target/classes/com/example/demo/DemoApplication.class
  // 类的基本信息
  Last modified 2023年8月30日; size 1737 bytes
  MD5 checksum 4bcbd7dbaf186b15f373d2b1321692b7
  Compiled from "DemoApplication.java"
public class com.example.demo.DemoApplication  
  minor version: 0
  major version: 55                            
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER        
  this_class: #14                         // com/example/demo/DemoApplication
  super_class: #15                        // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 3
// 常量池
Constant pool:
   #1 = Methodref          #15.#42        // java/lang/Object."<init>":()V
   #2 = String             #43            // a
   #3 = String             #44            // b
   #4 = String             #45            // ab
   #5 = InvokeDynamic      #0:#49         // #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
   #6 = Methodref          #9.#50         // java/lang/String.intern:()Ljava/lang/String;
   #7 = Fieldref           #51.#52        // java/lang/System.out:Ljava/io/PrintStream;
   #8 = Methodref          #38.#53        // java/io/PrintStream.println:(Z)V
   #9 = Class              #54            // java/lang/String
  #10 = String             #55            // c
  #11 = Methodref          #9.#56         // java/lang/String."<init>":(Ljava/lang/String;)V
  #12 = String             #57            // d
  #13 = String             #58            // cd
  #14 = Class              #59            // com/example/demo/DemoApplication
  #15 = Class              #60            // java/lang/Object
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               LocalVariableTable
  #21 = Utf8               this
  #22 = Utf8               Lcom/example/demo/DemoApplication;
  #23 = Utf8               main
  #24 = Utf8               ([Ljava/lang/String;)V
  #25 = Utf8               args
  #26 = Utf8               [Ljava/lang/String;
  #27 = Utf8               s1
  #28 = Utf8               Ljava/lang/String;
  #29 = Utf8               s2
  #30 = Utf8               s3
  #31 = Utf8               s4
  #32 = Utf8               s5
  #33 = Utf8               s6
  #34 = Utf8               x2
  #35 = Utf8               x1
  #36 = Utf8               StackMapTable
  #37 = Class              #26            // "[Ljava/lang/String;"
  #38 = Class              #61            // java/io/PrintStream
  #39 = Utf8               MethodParameters
  #40 = Utf8               SourceFile
  #41 = Utf8               DemoApplication.java
  #42 = NameAndType        #16:#17        // "<init>":()V
  #43 = Utf8               a
  #44 = Utf8               b
  #45 = Utf8               ab
  #46 = Utf8               BootstrapMethods
  #47 = MethodHandle       6:#62          // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;
[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #48 = String             #63            // \u0001\u0001
  #49 = NameAndType        #64:#65        // makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #50 = NameAndType        #66:#67        // intern:()Ljava/lang/String;
  #51 = Class              #68            // java/lang/System
  #52 = NameAndType        #69:#70        // out:Ljava/io/PrintStream;
  #53 = NameAndType        #71:#72        // println:(Z)V
  #54 = Utf8               java/lang/String
  #55 = Utf8               c
  #56 = NameAndType        #16:#73        // "<init>":(Ljava/lang/String;)V
  #57 = Utf8               d
  #58 = Utf8               cd
  #59 = Utf8               com/example/demo/DemoApplication
  #60 = Utf8               java/lang/Object
  #61 = Utf8               java/io/PrintStream
  #62 = Methodref          #74.#75        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Objec
t;)Ljava/lang/invoke/CallSite;
  #63 = Utf8               \u0001\u0001
  #64 = Utf8               makeConcatWithConstants
  #65 = Utf8               (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #66 = Utf8               intern
  #67 = Utf8               ()Ljava/lang/String;
  #68 = Utf8               java/lang/System
  #69 = Utf8               out
  #70 = Utf8               Ljava/io/PrintStream;
  #71 = Utf8               println
  #72 = Utf8               (Z)V
  #73 = Utf8               (Ljava/lang/String;)V
  #74 = Class              #76            // java/lang/invoke/StringConcatFactory
  #75 = NameAndType        #64:#80        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;       
  #76 = Utf8               java/lang/invoke/StringConcatFactory
  #77 = Class              #82            // java/lang/invoke/MethodHandles$Lookup
  #78 = Utf8               Lookup
  #79 = Utf8               InnerClasses
  #80 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #81 = Class              #83            // java/lang/invoke/MethodHandles
  #82 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #83 = Utf8               java/lang/invoke/MethodHandles

// 类的方法信息
{
  // 类的构造方法
  public com.example.demo.DemoApplication();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/demo/DemoApplication;

  // 类的main方法
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=9, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: aload_1
        10: aload_2
        11: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        16: astore        4
        18: ldc           #4                  // String ab
        20: astore        5
        22: aload         4
        24: invokevirtual #6                  // Method java/lang/String.intern:()Ljava/lang/String;
        27: astore        6
        29: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        32: aload_3
        33: aload         4
        35: if_acmpne     42
        38: iconst_1
        39: goto          43
        42: iconst_0
        43: invokevirtual #8                  // Method java/io/PrintStream.println:(Z)V
        46: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        49: aload_3
        50: aload         5
        52: if_acmpne     59
        55: iconst_1
        56: goto          60
        59: iconst_0
        60: invokevirtual #8                  // Method java/io/PrintStream.println:(Z)V
        63: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        66: aload_3
        67: aload         6
        69: if_acmpne     76
        72: iconst_1
        73: goto          77
        76: iconst_0
        77: invokevirtual #8                  // Method java/io/PrintStream.println:(Z)V
        80: new           #9                  // class java/lang/String
        83: dup
        84: ldc           #10                 // String c
        86: invokespecial #11                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        89: new           #9                  // class java/lang/String
        92: dup
        93: ldc           #12                 // String d
        95: invokespecial #11                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        98: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
       103: astore        7
       105: ldc           #13                 // String cd
       107: astore        8
       109: aload         7
       111: invokevirtual #6                  // Method java/lang/String.intern:()Ljava/lang/String;
       114: pop
       115: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       118: aload         8
       120: aload         7
       122: if_acmpne     129
       125: iconst_1
       126: goto          130
       129: iconst_0
       130: invokevirtual #8                  // Method java/io/PrintStream.println:(Z)V
       133: return
      LineNumberTable:
        line 6: 0
        line 7: 3
        line 8: 6
        line 9: 9
        line 10: 18
        line 11: 22
        line 14: 29
        line 15: 46
        line 16: 63
        line 18: 80
        line 19: 105
        line 20: 109
        line 23: 115
        line 24: 133
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0     134     0  args   [Ljava/lang/String;
            3     131     1    s1   Ljava/lang/String;
            6     128     2    s2   Ljava/lang/String;
            9     125     3    s3   Ljava/lang/String;
           18     116     4    s4   Ljava/lang/String;
           22     112     5    s5   Ljava/lang/String;
           29     105     6    s6   Ljava/lang/String;
          105      29     7    x2   Ljava/lang/String;
          109      25     8    x1   Ljava/lang/String;
      StackMapTable: number_of_entries = 8
        frame_type = 255 /* full_frame */
          offset_delta = 42
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream ]
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 255 /* full_frame */
          offset_delta = 51
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java
/lang/String ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java
/lang/String ]
          stack = [ class java/io/PrintStream, int ]
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "DemoApplication.java"
InnerClasses:
  public static final #78= #77 of #81;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #47 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invok
e/CallSite;
    Method arguments:
      #48 \u0001\u0001

通过跟踪main方法中的执行分析JVM指令执行过程

  1. 0: ldc #2 //从常量池#2的地址中加载得到a符号,并转为a字符串,在StringTable中寻找"a",如果没有找到,则将"a"放入到StringTable中。
  2. 2: astore_1 // 将上一步得到的"a"存入到LocalVariableTable中Slot=1的位置上。
  3. 3: ldc #3 // 从常量池#3的地址中加载得到b符号,并转为b字符串,在StringTable中寻找"b",如果没有找到,则将"b"放入到StringTable中。
  4. 5: astore_2 // 将上一步得到的"b"存入到LocalVariableTable中Slot=2的位置上。
  5. 6: ldc #4 // 从常量池#4的地址中加载得到ab符号,并转为ab字符串,在StringTable中寻找"ab",如果没有找到,则将"ab"放入到StringTable中。
  6. 8: astore_3 // 将上一步得到的"ab"存入到LocalVariableTable中Slot=3的位置上。
  7. 9: aload_1 // 从LocalVariableTable中Slot=1的位置获取数据,得到"a"。
  8. 10: aload_2 // 从LocalVariableTable中Slot=2的位置获取数据,得到"b"。
  9. 11: invokedynamic #5, 0 // String字符串连接,生成一个新的String对象,存放在堆中。
  10. 16: astore 4 // 将上一步得到的存入到LocalVariableTable中Slot=4的位置上。
  11. 18: ldc #4 //从常量池#4的地址中加载得到ab符号,并转为ab字符串,在StringTable中寻找"ab",如果没有找到,则将"ab"放入到StringTable中。
  12. 20: astore 5 // 将上一步得到的"ab"存入到LocalVariableTable中Slot=5的位置上。
  13. 22: aload 4 // 从LocalVariableTable中Slot=4的位置获取数据,得到String对象"ab"。
  14. 24: invokevirtual #6 // 调用String.intern方法,比较StringTable中是否存在相同字符串值的数据,如果没有则加入到StringTable中,并返回StringTable中的数据。
  15. 27: astore 6 // 将上一步得到的数据存入到LocalVariableTable中Slot=6的位置上。

得到答案

  • System.out.println(s3 == s4);

    false,s3在StringTable中,s4对象在堆中。

  • System.out.println(s3 == s5);

    true,生成对象s3时将字符串"ab"存入StringTable,生成对象s5时,先遍历StringTable得到"ab"并指给s5。所以s3与s5都指向StringTable中的"ab"。

  • System.out.println(s3 == s6);

    true,s3,s6都指向StringTable中的同一个数据。

  • System.out.println(x1 == x2);

    false,x2.intern()时,StringTable中已经存在"cd",就不会将x2放入StringTable中,所以x1指向StringTable,而x2指向堆内存。

  • 调换了最后两行代码的位置后,System.out.println(x1 == x2);

    true,x2.intern()时,StringTable中不存在"cd",于是将"cd"放入StringTable中并且x2指向了它,后来x1在StringTable中找到"cd"也指向它,所以x1与x2指向的是同一个地址。

  • 使用JDK1.6, System.out.println(x1 == x2);

    不管最后两行代码的位置如何,结果都是false。因为如果StringTable中不存在"cd",会将x2复制一份后放入到StringTable中,x2仍然在堆中。

总结
  • 常量池中字符串仅是符号,第一次用到时才变成对象。
  • 利用串池的机制,来避免重复创建字符串对象。
  • 字符串变量拼接的原理是StringBuilder(1.8)。
  • 字符串常量拼接的原理是编译期优化。
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池。
    • 1.8将字符串对象尝试放入串池,如果已经存在则并不会放入,如果没有则放入串池,并把串池中的对象返回。
    • 1.6将字符串对象尝试放入串池,如果已经存在则并不会放入,如果没有则会把此对象复制一份放入串池,并把串池中的对象返回。

6.5 StringTable性能调优

1. 调整-XX:StringTableSize=桶个数

StringTable的底层数据结构是HashTable,其实就是数组+链表的形式。当桶个数越多时,发生hash碰撞的概率就越小,插入数据的性能就会越高;相反桶个数越少,发生hash碰撞的概率就越大,插入数据的性能就会越低。

所示适当把StringTableSize调大可以提高插入数据的效率。

2. 考虑字符串对象是否入池

如果程序中有大量并且重复的字符串对象,可以考虑使用String.intern()将字符串入池,减少对堆内存空间的占用。