JVM 中的StringTable
是什么
字符串常量池是 JVM 中的一个重要结构,用于存储JVM运行时产生的字符串。在JDK7之前在方法区中,存储的是字符串常量。而字符串常量池在 JDK7 开始移入堆中,随之而来的是除了存储字符串常量外,还可以存储字符串引用(因为在堆中,引用堆中的字符串常量很方便,所以可以存储引用)。这使得很多字符串的操作在 JDK7 中和在之前的版本中执行是不同的结果。这也是为什么字符串相关的问题是如此具有迷惑性的原因之一。
底层
String:在 JDK9 之前,String 底层是使用 char 数组来存储字符串数据的,而在 JDK9 开始,使用 byte 数组+编码来代替 char 数组,这是为了节省空间,因为不同编码的数据占空间不一样,很多单位数据只需要一个 byte(8字节) 就可以存储,而使用 char(16字节)就会浪费多余的空间。
字符串常量池:底层使用 HashTable 来存储字符串,在 JDK6 HashTable 的数组长度是1006,JDK7 开始变成了 60013,这是为了避免存储字符串过多导致链表长度过长从而查询效率降低。可以使用参数 -XX:StringTableSize= 来设置 StringTable 数组的长度。
常见问题
字符串相加
1、对于字符串常量相加,编译器会优化成直接相加。
如 String ss = "a" + "b",在编译器的优化下,实际上只会创建一个 "ab" 字符串。
而 final String s1 = "a"; String s2 = s1+"b",除了创建字符串 "a" 外,只会创建 "ab"。
操作相关字符串如下:
可以看到只对字符串 "a"、"ab" 进行了入池操作(ldc)
2、对于包含字符串变量的相加,不会在字符串常量池中创建对应的字符串。
如 String s1 = "a"; String s2 = s1 + "b",执行完后字符串常量池中只会包含 "a"、"b" 字符串。
对于 s1 + "b",下面是其字节码操作
可以看到,相加操作实际上是调用 StringBuilder 的append 方法进行字符串拼接,然后调用它的 toString 方法获取返回值保存输出,期间并没有入池操作(ldc)。
由此得出的优化建议:因为每次执行一次包含非常量的字符串相加时,都进行了一次 StringBuilder 对象的创建,所以如果需要多次连接,可以直接创建 StringBuilder 对象,使用一个 StringBuilder 对象进行字符串拼接,避免创建多个对象降低效率。
对象创建数量
对象,包括 new 的对象以及字符串对象。
1、对于String ss = new String ("ab"),这个过程首先会在会在字符串常量池中创建一个 "ab" 字符串常量,然后再在堆上创建一个 new String() 的对象,在这个对象中会保存常量池中 "ab" 的地址信息,最后在栈上创建一个局部变量 ss ,保存堆中创建的对象地址。所以全程创建了堆中的一个对象和字符串常量池中的一个对象。
2、new String("a") + new String("b")。严格来看,创建了六个对象。
首先new String("a") 和 new String("b") ,分为创建了两个对象。两者相加时,会创建一个 StringBuilder 对象,而在 StringBuilder.toString()方法中,也会创建一个 String 对象
3、String s1 = "a", String s2 = "b", String s3 = "a" + "b" + s1 + "c" + s2; 对应的字节码如下:
字符串常量池中会有四个字符串对象,分别是 "a"、"b"、"ab"、"c"。在开始因为 s1、s2 的赋值,会将 "a"、"b" 分别加入字符串常量池,然后执行第三步,运行顺序是从左到右,首先执行 "a" + "b" ,因为两个都是常量,所以会因为编译器的优化直接返回 "ab",并且因为计算的两个参数都是常量,所以直接加入字符串常量池,随后因为与变量 s1 相加,所以调用 StringBuilder的append 方法,得到的结果保存到局部变量表中,所以引入常量 "c",因为是常量,所以还是会引入字符串常量池,然后与前面拼接得到的结果再次拼接,最后再与变量 s2 相加,因为不是常量所以还是不会将结果加入字符串常量池。
除此之外,还需要注意,上面三种情况是在初始情况下,也就是字符串常量池中没有要加入的字符串时的场景,如果字符串常量池中预先就包含要加入的字符串,那么就会直接将常量池中的对应的字符串地址返回给调用方。比如 String s1 = "a",在常量池中没有 "a" 时,创建的对象是 1个,而如果常量池中已经存在,那么就会将其地址直接返回赋给 s1。那么创建的对象就是 0个了。
intern() 与字符串相等判断
intern() 方法是 String 类的一个native方法,作用是尝试将调用这个方法的字符串对象加入字符串常量池中,然后返回常量池中存储的值。在开头说过,在 JDK7 开始字符串常量池可以存储字符串引用,导致字符串操作的过程可能会之前不一样,从而得到不同的结果。
intern() 方法的执行:
1.6 及之前:尝试将当前字符串常量加入常量池,如果常量池存在就返回地址值;如果不存在就先加入常量池,然后再返回加入位置的地址值。
1.7开始:尝试将当前字符串常量加入常量池,如果存在就将返回地址值;如果不存在就存入当前 String 字符串的地址值。
下面以一个例子来解释一下,在JDK7和JDK7之前下面代码执行分别是什么结果。
1 @Test
2 public void test1(){
3 String s = new String("1");
4 s.intern();
5 String s2 = "1";
6 System.out.println(s == s2);
7
8
9 String s3 = new String("1") + new String("1");
10 s3.intern();
11 String s4 = "11";
12 System.out.println(s3 == s4);
13 }
先说结论:
JDK7 之前: false、false。
JDK7 及之后:false、true。
原因:
1、首先先看上面 3------6 行的,首先,第三行会在字符串常量池中添加 "1" ,然后在堆中创建一个对象,保存 "1" 在常量池中的地址,再在局部变量表中添加一个 s 保存堆中对象的地址。随后执行第四行,此时 s 指向的字符串已经在常量池中了,所以这一步无效,第五行因为常量池已经存在 "1" ,所以 JDK7或之前执行的逻辑是一样的,直接将 "1" 在常量池中的地址返回给 s2。然后判断,s 指向的是堆中的对象,而 s2 指向的是常量池中的字符串常量,所以无论是 JDK7 还是之前的都是 false。
2、然后再看下面 9-----12 行。因为前面已经在常量池中添加 "1",所以第9行会直接返回地址,然后执行添加操作,创建字符串 "11",此时并没有添加到常量池,然后执行第10行,因为常量池不存在 "11",所以 JDK7 之前直接加入常量池,JDK7 及以后则直接将 "11" 的地址存入常量池,而 s3 则不变,还是保存的是常量池外的那个 "11" 的地址值。然后执行 11 行,因为常量池已存在 "11",所以 s4 就是返回 "11" 的地址值,不同的是在 JDK7 之前因为常量池保存的是 "11" 常量,所以返回的是常量池中的地址值;而 JDK7 及以后常量池保存的是常量池外的 "11" 的地址值,所以返回的是池外的地址值。所以最后判断在 JDK7 之前是 false,而在 JDK7 开始是 true。
JVM 中的StringTable的更多相关文章
- 阿里面试官:字符串在JVM中如何存放?90%的人就真的只回答在哪里存放
目录: 一道面试题的引出 案例分析 intern 源码分析 总结 1. 一道面试题的引出 在面试BAT这种一线大厂时,如果面试官问道:字符串在 JVM 中如何存放?大多数人能顺利的给出如下答案: 字符 ...
- 学习一下 JVM (二) -- 学习一下 JVM 中对象、String 相关知识
一.JDK 8 版本下 JVM 对象的分配.布局.访问(简单了解下) 1.对象的创建过程 (1)前言 Java 是一门面向对象的编程语言,程序运行过程中在任意时刻都可能有对象被创建.开发中常用 new ...
- JVM中的常量池详解
在Java的内存分配中,总共3种常量池: 转发链接:https://blog.csdn.net/zm13007310400/article/details/77534349 1.字符串常量池(Stri ...
- JVM中的常量池
在Java的内存分配中,总共3种常量池: ref:https://blog.csdn.net/zm13007310400/article/details/77534349 1.字符串常量池(Strin ...
- 【转】JVM运行原理及JVM中的Stack和Heap的实现过程
来自: http://blog.csdn.net//u011067360/article/details/46047521 Java语言写的源程序通过Java编译器,编译成与平台无关的‘字节码程序’( ...
- jvm中的年轻代 老年代 持久代 gc
虚拟机中的共划分为三个代:年轻代(Young Generation).老年代(Old Generation)和持久代(Permanent Generation).其中持久代主要存放的是Java类的类信 ...
- JVM中对象的创建过程
JVM中对象的创建过程如以下流程图中所示: 对其主要步骤进行详细阐述: 为新生对象分配内存: 内存的分配方式: 指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存放在一边,空闲的内存在另一边, ...
- 浅析JVM中的GC日志
目录 一.GC日志的格式分析 二.运行时开启GC日志 一.GC日志的格式分析 在讲述GC日志之前,我们先来运行下面这段代码 package com.example; public class Test ...
- JVM中的垃圾收集算法和Heap分区简记
如何判断垃圾对象? 垃圾收集的第一步就是先需要算法来标记哪些是垃圾,然后再对垃圾进行处理. 引用计数(ReferenceCounting)算法 这种方法比较简单直观,FlashPlayer/Pyt ...
随机推荐
- python爬取QQVIP音乐
QQ音乐相比于网易云音乐加密部分基本上没有,但是就是QQ音乐的页面与页面之间的联系太强了,,导致下载一个音乐需要分析前面多个页面,找数据..太繁琐了 1.爬取链接:https://y.qq.com/ ...
- 踏上Revit二次开发之路 2 从“HelloWorld”入手
2 从"HelloWorld"入手 在欧特克的官方网页上有个叫<My First Plug-in Training>的项目,号称可以让一个完全没有编程基础的人照着做出一 ...
- 国产网络测试仪MiniSMB - 如何配置VLAN数据流
国产网络测试仪MiniSMB(www.minismb.com)是复刻smartbits的IP网络性能测试工具,是一款专门用于测试智能路由器,网络交换机的性能和稳定性的软硬件相结合的工具.可以通过此以太 ...
- Kubernets二进制安装(8)之部署四层反向代理
四层反向代理集群规划 主机名 角色 IP地址 mfyxw10.mfyxw.com 4层负载均衡(主) 192.168.80.10 mfyxw20.mfyxw.com 4层负载均衡(从) 192.168 ...
- dll的注册与反注册
regsvr32.exe是32位系统下使用的DLL注册和反注册工具,使用它必须通过命令行的方式使用,格式是:regsvr32 [/i[:cmdline]] DLL文件名命令可以在"开始→运行 ...
- woj1005-holding animals-01pack woj1006-Language of animals-BFS
title: woj1005-holding animals-01pack date: 2020-03-05 categories: acm tags: [acm,woj,pack] 01背包.中等题 ...
- Battery API All In One
Battery API All In One https://caniuse.com/?search=Battery navigator.getBattery() /* Promise {<pe ...
- JavaScript interview Question - Create a Array with two papameters without using loop!
JavaScript interview Question - Create a Array with two papameters without using loop! JavaScript - ...
- DOH & TRR & HTTPS & DNS
DOH & TRR & HTTPS & DNS DNS over HTTPS Trusted Recursive Resolver DNS 解析过程图解 DNS 解析过程 递归 ...
- node.js & create file
node.js & create file node js create file if not exists https://nodejs.org/api/fs.html#fs_fs_ope ...