JVM笔记系列一 不一样的String

系列文章目录

待补充


这是第一篇,系列文章目录待补充


前言

      JVM作为Java已经封装好的一部分,自行控制垃圾的回收、管理内存,一般很少需要开发者去控制,但是遇到一些内存溢出、内存泄露,就需要掌握JVM的一些知识了,才能分析问题,进行优化。


提示:以下是本篇文章正文内容,会结合实际代码来分析,使用的Java版本为Java8

一、JVM是什么?

JVM运行时数据区域划分
      JVM即Java内存模型,其具体又可细分为五部分,分别为方法区、堆、虚拟机栈、本地方法栈跟程序计数器,前面的方法区与堆是所有线程共享的区域。
说明:JVM的五部分暂时不展示说明,本文侧重中Java7之后方法区的一个变化情况。

二、方法区中的变化

1.Java7开始变革

      到了JDK7的HotSpot,已经把原本放在方法区(永久代)的字符串常量池、静态变量等移出,而到了JDK8,终于完成废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Meta-space)来代替,把JDK7中永久代还剩余的内容(只要是类型信息)全部都移动元空间中。
-------------------------------------------------- 引用自 深入理解Java虚拟机_JVM高级特性与最佳实践_第3版_周志明 P77

      理解一下,就是JDK8之后,去掉 字符串常量池 与静态变量的方法区变成了元空间。元空间不再与堆连续,而是存在于本地内存(Native memory),能有效避免OOM的发生。

具体如下图所示:
Java8的元空间与堆

2.对String的一些影响

public static void StringTest() {
    String str1 = new StringBuilder("计算机").append("软件").toString();
    System.out.println(str1.intern() == str1);

    String str2 = new StringBuilder("ja").append("va").toString();
    System.out.println(str2.intern() == str2);
}

      以上的代码,在Java6中运行是 false false,在Java7中运行的结果是 true false
      这两个测试用例的要点在于:
      str1初始不存在于常量池,即不在常量池的对象调用 String.intern()方法,在不同的jdk版本的结果具有差异性。
      str2初始就存在于常量池,即存在常量池的对象在不同的jdk版本调用 String.intern()方法后,一直返回的是常量池中对象的地址。

String.intern()是本地方法,会返回String对象在字符串常量池中的引用地址。
如果字符串常量池包含此对象,直接返回;如果不包括,则会添加进字符串常量池,再返回常量池中的地址

      str1测试结果的差异在于:
      Java7之后,字符串常量池以及静态变量转移到了堆中,因此对于存在于堆中但不存在与字符串常量池的对象,加入方式发生了变化:
       不再会重新在字符串常量池中创建一遍 ,而是指向于堆中已有的那个对象,即String.intern()返回的常量池中对象地址就是堆中的对象地址。

public static void StringTest() {
    //1.6之后的字符串常量池就在堆中 intern的对象不存在常量池,这时的加入就是指向堆中的对象地址,因此返回的引用就是String的字符串实例地址
    String str1 = new StringBuilder("计算机").append("软件").toString();
    //true
    System.out.println(str1.intern() == str1);

    //Java字符串在str2对象前就存在于常量池了(加载sun.misc.Version这个类时), str2.intern()返回的是常量池中字符串的地址  str2返回的是新创建string对象的地址
    String str2 = new StringBuilder("ja").append("va").toString();
    //str3是常量池中的地址 str4是新建string对象的地址,与str2也不一样
    String str3 = "java";
    String str4 = new String("java");
    //false
    System.out.println(str2.intern() == str2);
    //true
    System.out.println(str2.intern() == str3);
    //false
    System.out.println(str2.intern() == str4);
}

三、String不同创建方式的区别变化

1.new String(“19”); 与 String s1 = “19”;

创建时为了避免直接引用常量池的字符了,将创建不相等的字符串,以下例子都是如此。

public static void String1() {
    System.out.println("  ===========创建字符对象============");
    String s2 = new String("19");

    System.out.println("  ===========直接创建============");
    String s3 = "28";
}

相同点:
       s2、s3在创建时都会将相应的字符串加入到常量池
差异点:
       s2会在堆中创建对象,指向常量池中;s3不会在堆中创建堆中,而是在虚拟机栈中存在

2. new StringBuilder(“1”).append(“1”).toString(); 与 new String(“1”) + new String(“1”);

public static void String1() {
    System.out.println("  ========字符StringBuilder append拼接开始创建=======");
    String s = new StringBuilder("计算机").append("软件").toString();

    System.out.println("  ===========字符串 + 拼接开始创建============");
    String s1 = new String("1") + new String("1");

}

结果:
       s、s1创建后都在堆中,创建过程中的常量字符串 “计算机” 、“软件”、“1”都在常量池中。并且String +创建时,会优化成StringBuilder append的方式进行创建。

3. 分析四种创建的字节码

public static void String3() {
    System.out.println("  ===========开始执行string3============");
    String str = new StringBuilder("计算机").append("软件").toString();

    System.out.println("  ===========字符串拼接开始创建============");
    String s1 = new String("1") + new String("1");

    System.out.println("  ===========创建字符对象============");
    String s2 = new String("19");

    System.out.println("  ===========直接创建============");
    String s3 = "28";
}

两种查看方式:

  1. javap -c 或者 javap -verbose 查看class文件对应的字节码
    例如 javap -verbose StringConstant.class
  2. 也可在idea软件中直接查看Java文件的字节码 ByteCode, Intellij IDEA 的一个工具菜单可以直接查看字节码,打开 ByteCode 插件窗口方法如下:View -> Show bytecode
    字节码1

code29后可以看到两个String对象+ 加号拼接,会优化成StringBuilder(“1”).append(“1”).toString()的方式

字节码2

code83可以看到 new String对象的创建方式比直接 “” 创建,多个一个初始化的过程

字符串常量池
创建过程中的常量都被加入到了字符串常量池中,并且new String 跟 “ ” 创建的结果也在会常量池中

总结

  • 两个String 对象 使用+ 加号拼接,会优化成StringBuilder(“计算机”).append(“软件”).toString(), 因此只要不在for循环中使用+拼接,其实使用+拼接字符串也可行,有JVM给你兜底优化。
  • new String(“19”); 与 String string13 = “19”; 创建时都会将相应的字符串加入到常量池中,但是第一个会在堆中创建对象,第二个不会
  • Java7之后,字符串常量池以及静态变量转移到了堆中,因此对于存在于堆中但不存在与字符串常量池的对象,调用String.intern()后 不再会重新在字符串常量池中创建一遍对象 ,而是直接指向于堆中已有的那个对象,即String.intern()返回的常量池中对象地址就是堆中的对象地址。

进阶点
new String( “ aa” + v); 创建中有常量字符 也有变量的时候

  1. 常量“ aa” 会加入到常量池,
  2. v 如果是String堆中的对象 (v不会在常量池 除非调用intern()方法);如果是 String v = “19”; 那么v在创建时已经在常量池。即这步中的v不在被加入到常量池中
  3. “ aa” + v, 都不会被加入到常量池
    在四种创建方式中,会将遇到的常量 直接加入到常量池中,最终的对象是否在常量池 看具体的创建方式而定。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值