一、内存结构的组成
二、程序计数器(PC Register)
定义
Program Counter Regiter 程序计数器(寄存器)
作用
程序计数器:记住下一条JVM指令的执行地址。如下图所示,0,3,5,8 就是JVM指令在内存中的地址,程序计数器记录的就是这些指令的执行地址。
程序计数器是由寄存器组成的。
特点
- 程序计数器是线程私有的。
- Java虚拟机规范规定,程序计数器不会存在内存溢出问题。
三、虚拟机栈(JVM Stacks)
3.1 定义
Java Virtual Machine Stacks Java虚拟机栈
- 虚拟机栈:提供了每个线程运行时需要的内存空间。
- 栈帧:每次方法运行时所占用的内存。如:参数,局部变量,返回地址。
- 一个虚拟机栈由多个栈帧(Frame)组成。
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法,就就是在栈顶部的栈帧。
- 栈帧以先进后出的顺序进出虚拟机栈。
如上图代码所示,方法调用链是main——> method1——> method2。
所以,入栈顺序依次是 main,method1,method2。
执行完的方法先出栈,所以出栈顺序依次是method2,method1,main。
问题辨析
-
垃圾回收是否涉及栈内存?
答:不涉及。因为栈帧在每次方法调用结束后会自动弹出栈,也就是会自动被回收掉,所以不需要垃圾回收。
-
栈内存分配越大越好吗?
答:使用-Xss命令设置栈内存的大小。栈内存越大,线程数越少。因为物理内存的大小是一定的,栈内存越大,可同时运行的线程数就越少。比如:物理内存总共有500M,栈内存是1M,这样可以有500个线程同时运行;但如果栈内存扩大到5M,只有100个线程可以同时运行。
一般采用系统默认的栈内存大小。Linux系统默认是1024KB。
-
方法内的局部变量是否线程安全?
- 如果方法内的局部变量没有逃离方法的作用范围,它是线程安全的。因为局部变量记录在每个栈帧内而且栈帧是线程私有的,所以每个线程都会单独持有一份局部变量。
- 如果局部变量引用了对象,并逃离了方法的作用范围,就需要考虑线程安全。
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占用过多
- 用top 命令定位哪个进程对CPU的占用过高。
如下图所示,PID=32655的进程占用了99.8%的CPU。
2. 用ps H -eo pid,tid,%cpu | grep pid(具体的进程id)命令进一步定位是哪个线程引起的cpu占用过高。
如下图所示,PID=32655,TID=32665的线程占用了99.5%的CPU。
- 用jstack pid 命令 可以看到进程PID下的所有线程,根据TID找到具体的线程,进一步定位到问题代码的源码行行号。
如下图所示,使用jstack命令后,可以看到PID=32655下的所有线程,然后根据TID=32665找到nid=0x7f99的线程,就是这个线程占用了99.5%的CPU。该线程的状态是RUNNABLE的,定位到有问题的代码是类Demo_16中的第8行。
案例 2:程序运行很长时间没有结果
- 使用 jstack命令 定位到线程死锁问题。
四、本地方法栈(Native Method Stacks)
本地方法栈给本地方法native method的运行提供内存空间。
五、堆(Heap)
5.1 定义
通过new关键字创建对象都会使用堆内存。
特点
- 它是线程共享的,堆中对象都需要考虑线程安全问题。
- 有垃圾回收机制。
5.2 堆内存溢出OutOfMemoryError: Java Heap Space
使用**命令-Xmx **设置堆内存的大小
5.3 堆内存诊断
1. jps 工具
查看当前系统中有哪些Java进程。
2. jmap 工具
使用jmap -heap PID命令查看某一个时刻堆内存使用情况。
3. jconsole 工具
图形界面的,多功能的监测工具,可以连续监测。查看堆内存使用量、线程数量、类数量、CPU使用率、监测死锁,执行GC等功能。
4. jvisualvm 工具
图形界面的工具,比jconsole还要强大,除了具备jconsole工具的功能外,还有一个堆Dump功能,可以抓取某一时刻堆内存的快照,并查看占用堆内存最高的对象是哪个。
六、方法区(Method Area)
6.1 定义
- 方法区是线程共享的。
- 在虚拟器启动时被创建。
- 方法区在逻辑上是堆的组成部分。
- 方法区会产生内存溢出。
6.2 组成
- ClasslLoader: 可以用来加载类的二进制字节码
6.3 方法区内存溢出
- 1.8以前会导致永久代内存溢出 OutOfMemoryError: PermGen space
- XX: MaxPermSize=8m
- 1.8以后会导致元空间内存溢出 OutOfMemoryError: Metaspace
- 默认情况下,元空间没有设置大小上限。 -XX:MaxMetaspaceSize=8m,通过这个命令可以设置元空间的最大内存空间。
6.3 运行时常量池
- 常量池:使用javap -v 命令得到的一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息。
- 运行时常量池:常量池是存在于*.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命令反编译,得到下面的内容,分为几个大类:
- 类的基本信息,包括最近修改时间、MD5校验码、源文件名称、JDK版本、父类、接口等信息。
- 常量池。
- 类的方法及执行指令,具体包括:
- 所有方法及执行指令。
- LineNumberTable 行号表。
- LocalVariableTable 本地变量表。
- StackMapTable。
- InnerClasses。
- 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指令执行过程
- 0: ldc #2 //从常量池#2的地址中加载得到a符号,并转为a字符串,在StringTable中寻找"a",如果没有找到,则将"a"放入到StringTable中。
- 2: astore_1 // 将上一步得到的"a"存入到LocalVariableTable中Slot=1的位置上。
- 3: ldc #3 // 从常量池#3的地址中加载得到b符号,并转为b字符串,在StringTable中寻找"b",如果没有找到,则将"b"放入到StringTable中。
- 5: astore_2 // 将上一步得到的"b"存入到LocalVariableTable中Slot=2的位置上。
- 6: ldc #4 // 从常量池#4的地址中加载得到ab符号,并转为ab字符串,在StringTable中寻找"ab",如果没有找到,则将"ab"放入到StringTable中。
- 8: astore_3 // 将上一步得到的"ab"存入到LocalVariableTable中Slot=3的位置上。
- 9: aload_1 // 从LocalVariableTable中Slot=1的位置获取数据,得到"a"。
- 10: aload_2 // 从LocalVariableTable中Slot=2的位置获取数据,得到"b"。
- 11: invokedynamic #5, 0 // String字符串连接,生成一个新的String对象,存放在堆中。
- 16: astore 4 // 将上一步得到的存入到LocalVariableTable中Slot=4的位置上。
- 18: ldc #4 //从常量池#4的地址中加载得到ab符号,并转为ab字符串,在StringTable中寻找"ab",如果没有找到,则将"ab"放入到StringTable中。
- 20: astore 5 // 将上一步得到的"ab"存入到LocalVariableTable中Slot=5的位置上。
- 22: aload 4 // 从LocalVariableTable中Slot=4的位置获取数据,得到String对象"ab"。
- 24: invokevirtual #6 // 调用String.intern方法,比较StringTable中是否存在相同字符串值的数据,如果没有则加入到StringTable中,并返回StringTable中的数据。
- 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()将字符串入池,减少对堆内存空间的占用。