首页 > 系统相关 >Java性能优化-String的intern()方法的使用减少内存消耗

Java性能优化-String的intern()方法的使用减少内存消耗

时间:2024-07-22 10:08:50浏览次数:14  
标签:Java String 对象 intern new 字符串 霸道

场景

String.intern()

String.intern() 方法用于在字符串常量池中查找是否存在与指定字符串相等的字符串。

如果找到了,就返回该字符串的引用;否则,就在字符串常量池中创建一个新的字符串对象,并返回对它的引用。

这个方法对于避免创建重复的字符串对象非常有用,特别是在处理大量字符串数据时,可以显著减少内存使用。

需要明确Java(这里是JDK1.8)创建字符串的不同:

1.使用双引号声明的字符串对象会保存在字符串常量池中

2.使用new关键字创建的字符串对象会先从字符串常量池中找,如果没找到就创建一个,

然后再在堆中创建字符串对象;如果找到了,就直接在堆中创建字符串对象

看下面的两段代码,猜测下运行结果

        String str1 = new String("公众号:霸道的程序猿");
        String str2 = str1.intern();
        System.out.println(str1 == str2);

这段运行结果为false

第一行,字符串常量池中会先创建一个公众号:霸道的程序猿的对象,

然后堆中会再创建一个公众号:霸道的程序猿的对象,str1引用的是堆中的对象。

第二行,str1执行intern()方法,该方法会从字符串常量池中查找公众号:霸道的程序猿,

常量池中存在公众号:霸道的程序猿字符串是因为第一行代码已经在字符串常量池创建公众号:霸道的程序猿字符串,

所以str2引用的是字符串常量池中的对象。str1 和 str2 的引用地址是不同的,str1一个来自堆,str2一个来自字符串常量池,

所以输出的结果为 false。

这里为什么还会再堆中再创建一个公众号:霸道的程序猿的对象,是因为:

Java 7之后,由于字符串常量池被移动到了堆中,执行String.intern()方法时,

如果堆中已经存在了该对象,字符串常量池中就不会创建新的对象,而是直接保存堆中对象的引用。

这一优化节省了一部分内存空间。

那么再看下面的代码:

        String str3 = new String("公众号")+new String("霸道的程序猿");
        String str4 = str3.intern();
        System.out.println(str3 == str4);

输出结果为:true

第一行,字符串常量池中会先创建两个对象公众号和霸道的程序猿,然后堆中会再创建两个匿名对象公众号和霸道的程序猿,

最后还有一个公众号霸道的程序猿,str3引用的是堆中公众号霸道的程序猿对象。

第二行,str3 执行intern()方法,该方法会从字符串常量池中查找公众号霸道的程序猿对象是否存在,此时字符串常量池不存在,但是堆中存在

字符串常量池中保存的是堆中公众号霸道的程序猿对象的引用,也就是说,str3和str4的引用地址是相同,所以输出的结果为 true。

具体步骤如下:

创建 "公众号" 字符串对象,存储在字符串常量池中。

创建 "霸道的程序猿" 字符串对象,存储在字符串常量池中。

执行 new String("公众号"),在堆上创建一个字符串对象,内容为 "公众号"。

执行 new String("霸道的程序猿"),在堆上创建一个字符串对象,内容为 "霸道的程序猿"。

执行 new String("公众号") + new String("霸道的程序猿"),会创建一个 StringBuilder 对象,并将 "公众号" 和 "霸道的程序猿" 追加到其中,

然后调用 StringBuilder对象的ToString() 方法,将其转换为一个新的字符串对象,内容为 "公众号霸道的程序猿"。

这个新的字符串对象存储在堆上。

特别说明:

编译器遇到 + 号这个操作符的时候,会将 new String("公众号") + new String("霸道的程序猿") 编译代码如下:

new StringBuilder().append("公众号").append("霸道的程序猿").toString();

实际步骤如下:

创建一个 StringBuilder 对象。

StringBuilder 对象上调用 append("公众号"),将 "公众号" 追加到 StringBuilder 中。

在 StringBuilder 对象上调用 append("霸道的程序猿"),将 "霸道的程序猿" 追加到 StringBuilder 中。

在 StringBuilder 对象上调用 toString() 方法,将 StringBuilder 转换为一个新的字符串对象,内容为 "公众号霸道的程序猿"

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

实现

下面做一下在极端条件下使用String.intern()与不使用的区别

参考以上测试工具的使用,编写如下测试代码

import java.util.Random;
import java.util.concurrent.TimeUnit;

public class StringInternTest {

    static final int MAX = 1000 * 10000;
    static final String[] arr = new String[MAX];

    public static void main(String[] args) throws InterruptedException {

        Integer[] DB_DATA = new Integer[10];
        Random random = new Random(10 * 10000);
        for (int i = 0; i < DB_DATA.length; i++) {
            DB_DATA[i] = random.nextInt();
        }
        long t = System.currentTimeMillis();
        for (int i = 0; i < MAX; i++) {
            //arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));
            arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
        }

        System.out.println((System.currentTimeMillis() - t) + "ms");
        System.gc();

        TimeUnit.SECONDS.sleep(25);
    }
}

性能测试结果对比

不使用intern()方法

 

使用intern()方法

 

这里对比内存占用消耗工具的使用参考如下

JVM常用工具中jmap实现手动进行堆转储(heap dump文件)并使用MAT(Memory Analyzer Tool)进行堆分析-内存消耗分析:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/140549397

这个例子来源于美团技术团队提供,具体原理与流程可参考如下:

https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

另一个例子是推特优化发布消息使用String.intern()节省内存的操作:

文章可参考如下:

https://www.codecentric.de/wissens-hub/blog/save-memory-by-using-string-intern-in-java

这里简单记录下

每次发布消息状态的时候,都会产生一个地址信息,以当时 Twitter 用户的规模预估,服务器需要 32G 的内存来存储地址信息。

public class Location {
    private String city;
    private String region;
    private String countryCode;
    private double longitude;
    private double latitude;
}

考虑到其中有很多用户在地址信息上是有重合的,比如,国家、省份、城市等,

这时就可以将这部分信息单独列出一个类,以减少重复,代码如下:

public class SharedLocation {
    private String city;
    private String region;
    private String countryCode;
}
public class Location {
    private SharedLocation sharedLocation;
    double longitude;
    double latitude;
}

通过优化,数据存储大小减到了 20G 左右。但对于内存存储这个数据来说,依然很大,怎么办呢?

这个案例来自一位 Twitter 工程师在 QCon 全球软件开发大会上的演讲,他们想到的解决方法,

就是使用 String.intern 来节省内存空间,从而优化 String 对象的存储。

具体做法就是,在每次赋值的时候使用 String 的 intern 方法,如果常量池中有相同值,

就会重复使用该对象,返回对象引用,这样一开始的对象就可以被回收掉。

这种方式可以使重复性非常高的地址信息存储大小从 20G 降到几百兆。

SharedLocation sharedLocation = new SharedLocation();
sharedLocation.setCity(messageInfo.getCity().intern());
sharedLocation.setCount
sharedLocation.setRegion(messageInfo.getCountryCode().intern());

Location location = new Location();
location.set(sharedLocation);
location.set(messageInfo.getLongitude());
location.set(messageInfo.getLatitude());

使用 intern 方法需要注意的一点是,一定要结合实际场景。

因为常量池的实现是类似于一个 HashTable 的实现方式,HashTable 存储的数据越大,遍历的时间复杂度就会增加。

如果数据过大,会增加整个字符串常量池的负担。

对于程序中大量使用存在的字符串时,尤其存在很多已经重复的字符串时,使用intern()方法能够节省内存空间。

大的网站平台,需要内存中存储大量的字符串。比如社交网站,很多人都存储:北京市、海淀区等信息。

这时候如果字符串都调用intern()方法,就会很明显降低内存的大小。

标签:Java,String,对象,intern,new,字符串,霸道
From: https://www.cnblogs.com/badaoliumangqizhi/p/18315507

相关文章

  • Java基础面试题大全 -001
    1、Java语言有哪些特点1、简单易学、有丰富的类库2、面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高)3、与平台无关性(JVM是Java跨平台使用的根本)4、可靠安全5、支持多线程6、java生态完善2、面向对象和面向过程的区别面向过程:是分析解决问题的步骤,然后用函数......
  • JAVA:正则表达式匹配
    1.非捕获组(?:)/***根据正则表达式找到字符串中符合条件的字符段,并输出到控制台*/publicstaticvoidmatch(Stringregex,Stringstr){Patternpattern=Pattern.compile(regex);Matchermatcher=pattern.matcher(str);while(matcher.find()){Syste......
  • IntelliJ IDEA 中 右键新建时,选项没有Java class的解决方法和具体解释
    右键新建没有java文件具体的解释和解决方案。 如上图红圈所示,我们可以根据对项目的任意目录进行这五种目录类型标注,这个知识点非常非常重要,必须会。Sources 一般用于标注类似 src 这种可编译目录。有时候我们不单单项目的 src 目录要可编译,还有其他一些特别的目录......
  • 运用Java打印金字塔
    1publicclassexercise05{2publicstaticvoidmain(String[]args){3//思路分析4//化繁为简5//1.打印一个矩形6//*****7//*****8//*****9//*****10//*****11//2.打印半个金字塔12//*//第一层有1个*13//**//......
  • Java开发者快速上手.NET指南
    前言最近有不少Java开发者、应届生加入了我们的DotNetGuide技术社区交流群(前5个群都已满500人,6群也即将满500人),经常看到有小伙伴在群里问:想要快速上手开发.NET有什么好的学习教程和资料可以参考借鉴的?今天大姚给大家分享一下Java开发者想要快速上手.NET有哪些教程和优质资料,希......
  • Java--接口
    目录语法规则例子实现多个接口接口之间的继承抽象类和接口的区别在现实生活中,接口的例子比比皆是,比如:电源插座,主机上的USB接口等。这些插口中可以插所有符合规范的设备。通过这个例子我们知道,接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在jav......
  • rabbitmq发送消息localdatetime报错:Java 8 date/time type `java.time.LocalDateTime`
    两种解决方案:通过全局配置LocalDateTime的序列化/***json序列化增强解决Jackson序列化不了Java8日期*/@BeanpublicMessageConvertermessageConverter(){ObjectMapperom=newObjectMapper();om.setVisibility(PropertyAccessor.ALL,JsonAut......
  • [Java源码]Object
    ClassObjectjava.lang.ObjectpublicclassObjectClassObjectistherootoftheclasshierarchy.EveryclasshasObjectasasuperclass.Allobjects,includingarrays,implementthemethodsofthisclass.Since:JDK1.0SeeAlso:ClassConstructorSumm......
  • Java学习日历(继承,多态)
    继承中成员变量访问特点:就近原则System.out.println(name)System.out.println(name)Sytem.out.println(this.name)从本类成员变量开始往上找Sytem.out.println(super.name)从父类成员变量开始往上找packageExtends;classStudent{publicvoideat(){System......
  • Java流的概念及API
    流的概念 流(Stream)的概念代表的是程序中数据的流通,数据流是一串连续不断的数据的集合。在Java程序中,对于数据的输入/输出操作是以流(Stream)的方式进行的。可以把流分为输入流和输出流两种。程序从输入流读取数据,向输出流写入数据。Java中的流可以按如下方式分类:按流的方......