浅谈Java中的内存分配

139 阅读3分钟

最近在整理面试题的时候,发现了这样一个问题…… 小问号,你是否也会有这样的朋友?

  • JVM内存总览 在这里插入图片描述 Java中的变量(包括对象变量)和基本类型的值存放于栈内存。 栈内存的特点之一就是共享数据,接下来举一个String的例子,来体会栈内存中的对象变量共享字符串常量池。

举个栗子

先上代码:

public class StringDemo {
    public static void main(String[] args) {
        String str1= "abc"; //1
        String str2= "abc"; //2
        String str3=new String("abc");//3
        String str4=new String("abc");  //4
        System.out.println(str1==str2);  //true
        System.out.println(str3 == str4);  //false
    }
}
  • 解释1:执行语句1时String str1= "abc",JVM首先会检查字符串常量池中是否存在该字符串对象
  1. 如果已经存在,那么就不会再创建了,直接返回该字符串常量池中的内存地址。
  2. 如果不存在,那么就会在字符串常量池中创建该字符串对象,然后返回该字符串常量池中的内存地址。

所以栈内存中的str1和str2的内存地址都是指向“abc”在字符串常量池中的位置。

  • 解释2:执行语句3String str3=new String("abc")时,JVM会首先检查字符串常量池中是否已经存在字符串对象
  1. 如果已经存在,则不会在字符串常量池中创建;如果不存在,则会在字符串常量池中创建“abc”字符串对象。
  2. 然后到堆内存中创建一份字符串对象,把字符串常量池中的“abc”字符串内容拷贝到堆内存中的字符串对象中。最后返回堆内存中该字符串的内存地址。

即栈内存中的str3和str4都指向堆内存中的对象。且str3和str4并未指向同一个对象。

关于字符串常量池到底在哪的问题?

引用—帅地

  • 在JDK1.8中,使用元空间代替永久代来实现方法区,但是方法区并没有改变。 所谓”Your father will always be your father”,变动的只是方法区中内容的物理存放位置。 正如上面所说,类型信息(元数据信息)等其他信息被移动到了元空间中;但是运行时常量池和字符串常量池被移动到了堆中。 但是不论它们物理上如何存放,逻辑上还是属于方法区的。

JDK1.8中字符串常量池和运行时常量池逻辑上属于方法区,但是实际存放在堆内存中,因此既可以说两者存放在堆中,也可以说两则存在于方法区中,这就是造成误解的地方。

在这里插入图片描述

题目——String相加的问题

在这里插入图片描述

引用——smartleon

  • 对于字符串常量的相加,在编译时直接将字符串合并,而不是等到运行时再合并。也就是说,String a = "tao" + "bao" ;和String a = "taobao" ;编译出的字节码是一样的。所以等到运行时,由于栈内存是数据共享原则,a和MESSAGE指向的是同一个字符串。 而对于后面的(b+c)又是什么情况呢?
  • Java对String的相加是通过StringBuffer实现的,先构造一个StringBuffer里面存放”tao”,然后调用append()方法追加”bao”,然后将值为”taobao”的StringBuffer转化成String对象。StringBuffer对象在堆内存中,那转换成的String对象理所应当的也是在堆内存中。

再把变量b和c的定义改一下:

final  Stringb =  "tao" ;
final  String c =  "bao" ;
System. out .println( (b+c)== MESSAGE );

现在b和c不可能再次赋值了,所以编译器将b+c编译成了”taobao”。因此,这时的结果是true。 在字符串相加中,只要有一个是非final类型的变量,编译器就不会优化,因为这样的变量可能发生改变,所以编译器不可能将这样的变量替换成常量。

如果觉得写的不错的话,欢迎一键三连~