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
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
6天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
4天前
|
Java Linux Windows
JVM内存
首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制。
8 1
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
60 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
存储 缓存 算法
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
|
23天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
45 10
|
22天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。