jvm之StringTable解读(二)

简介: jvm之StringTable解读(二)

代码举例4

1. public void test3(){
2. String s1 = "a";
3. String s2 = "b";
4. String s3 = "ab";
5. String s4 = s1 + s2;
6.     System.out.println(s3==s4);
7. }

我们拿例4的字节码进行查看,可以发现s1 + s2实际上是new了一个StringBuilder对象,并使用了append方法将s1和s2添加进来,最后调用了toString方法赋给s4

1. 0 ldc #2 <a>
2. 2 astore_1
3. 3 ldc #3 <b>
4. 5 astore_2
5. 6 ldc #4 <ab>
6. 8 astore_3
7. 9 new #5 <java/lang/StringBuilder>
8. 12 dup
9. 13 invokespecial #6 <java/lang/StringBuilder.<init>>
10. 16 aload_1
11. 17 invokevirtual #7 <java/lang/StringBuilder.append>
12. 20 aload_2
13. 21 invokevirtual #7 <java/lang/StringBuilder.append>
14. 24 invokevirtual #8 <java/lang/StringBuilder.toString>
15. 27 astore 4
16. 29 getstatic #9 <java/lang/System.out>
17. 32 aload_3
18. 33 aload 4
19. 35 if_acmpne 42 (+7)
20. 38 iconst_1
21. 39 goto 43 (+4)
22. 42 iconst_0
23. 43 invokevirtual #10 <java/io/PrintStream.println>
24. 46 return

1. 将字符串"a"压入操作数栈。
2. 通过字节码指令"astore_1"将字节码操作数栈顶的引用类型值(String类型)存储到局部变量表的第1个槽位(s1)。
3. 将字符串"b"压入操作数栈。
4. 通过字节码指令"astore_2"将字节码操作数栈顶的引用类型值(String类型)存储到局部变量表的第2个槽位(s2)。
5. 将字符串"ab"压入操作数栈。
6. 通过字节码指令"astore_3"将字节码操作数栈顶的引用类型值(String类型)存储到局部变量表的第3个槽位(s3)。
7. 通过字节码指令"new"创建StringBuilder对象。
8. 通过字节码指令"dup"复制该StringBuilder对象的引用,并将复制后的引用压入操作数栈顶。
9. 通过字节码指令"invokespecial"调用StringBuilder的构造器,并将s1压入操作数栈作为构造器参数。
10. 通过字节码指令"aload_2"将局部变量表中s2的值(引用类型)压入操作数栈,并在StringBuilder对象上调用append方法。
11. 通过字节码指令"invokevirtual"调用StringBuilder的toString方法,并将返回值("ab")压入操作数栈。
12. 将局部变量表中s4的值(引用类型)压入操作数栈。
13. 通过字节码指令"if_acmpne"比较操作数栈顶的两个引用类型值(s3和s4),如果不相等则跳转到16行。
14. 通过字节码指令"getstatic"获取System.out对象的引用,并将操作数栈顶的"true"值(int类型)强制转换为Object类型,然后调用println方法输出到控制台。
15. 通过字节码指令"goto"跳转到17行。
16. 通过字节码指令"getstatic"获取System.out对象的引用,并将操作数栈顶的"false"值(int类型)强制转换为Object类型,然后调用println方法输出到控制台。
17. 通过字节码指令"return"结束方法的执行。

字符串拼接操作性能对比

1. public class Test
2. {
3. public static void main(String[] args) {
4. int times = 50000;
5. 
6. // String
7. long start = System.currentTimeMillis();
8.         testString(times);
9. long end = System.currentTimeMillis();
10.         System.out.println("String: " + (end-start) + "ms");
11. 
12. // StringBuilder
13.         start = System.currentTimeMillis();
14.         testStringBuilder(times);
15.         end = System.currentTimeMillis();
16.         System.out.println("StringBuilder: " + (end-start) + "ms");
17. 
18. // StringBuffer
19.         start = System.currentTimeMillis();
20.         testStringBuffer(times);
21.         end = System.currentTimeMillis();
22.         System.out.println("StringBuffer: " + (end-start) + "ms");
23.     }
24. 
25. public static void testString(int times) {
26. String str = "";
27. for (int i = 0; i < times; i++) {
28.             str += "test";
29.         }
30.     }
31. 
32. public static void testStringBuilder(int times) {
33. StringBuilder sb = new StringBuilder();
34. for (int i = 0; i < times; i++) {
35.             sb.append("test");
36.         }
37.     }
38. 
39. public static void testStringBuffer(int times) {
40. StringBuffer sb = new StringBuffer();
41. for (int i = 0; i < times; i++) {
42.             sb.append("test");
43.         }
44.     }
45. }
46. 
47. // 结果
48. String: 7963ms
49. StringBuilder: 1ms
50. StringBuffer: 4ms

本实验进行5万次循环,String拼接方式的时间是StringBuilder.append方式的约8000倍,StringBuffer.append()方式的时间是StringBuilder.append()方式的约4倍

可以看到,通过StringBuilder的append方式的速度,要比直接对String使用“+”拼接的方式快的不是一点半点

那么,在实际开发中,对于需要多次或大量拼接的操作,在不考虑线程安全问题时,我们就应该尽可能使用StringBuilder进行append操作

除此之外,还有那些操作能够帮助我们提高字符串方面的运行效率呢?

StringBuilder空参构造器的初始化大小为16。那么,如果提前知道需要拼接String的个数,就应该直接使用带参构造器指定capacity,以减少扩容的次数

1. /**
2.  * Constructs a string builder with no characters in it and an
3.  * initial capacity of 16 characters.
4.  */
5. public StringBuilder() {
6. super(16);
7. }
8. 
9. /**
10.  * Constructs a string builder with no characters in it and an
11.  * initial capacity specified by the {@code capacity} argument.
12.  *
13.  * @param      capacity  the initial capacity.
14.  * @throws     NegativeArraySizeException  if the {@code capacity}
15.  *               argument is less than {@code 0}.
16.  */
17. public StringBuilder(int capacity) {
18. super(capacity);
19. }

intern()的使用

官方API文档中的解释

public String intern()

Returns a canonical representation for the string object.

A pool of strings, initially empty, is maintained privately by the class String.

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java™ Language Specification.

  • Returns:
    a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.

当调用intern方法时,如果池子里已经包含了一个与这个String对象相等的字符串,正如equals(Object)方法所确定的,那么池子里的字符串会被返回。否则,这个String对象被添加到池中,并返回这个String对象的引用。

由此可见,对于任何两个字符串s和t,当且仅当s.equals(t)为真时,s.intern() == t.intern()为真。

所有字面字符串和以字符串为值的常量表达式都是interned。

返回一个与此字符串内容相同的字符串,但保证是来自一个唯一的字符串池。

intern是一个native方法,调用的是底层C的方法

public native String intern();

如果不是用双引号声明的String对象,可以使用String提供的intern方法,它会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。

String myInfo = new string("I love atguigu").intern();

也就是说,如果在任意字符串上调用String.intern方法,那么其返回结果所指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是true

("a"+"b"+"c").intern() == "abc"

通俗点讲,Interned string就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)

相关文章
|
Java C++
jvm之StringTable解读(三)
jvm之StringTable解读(三)
|
存储 缓存 Oracle
|
存储 Java C++
JVM系列之:String.intern和stringTable
JVM系列之:String.intern和stringTable
JVM系列之:String.intern和stringTable
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
410 1
|
3月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
49 4
|
14天前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
2月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
27 3
|
2月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
59 1