Java内存分配一般涉及如下区域:
- 寄存器:程序中无法控制
- 栈:用于存放基本类型数据和对象的引用。
- 堆:存放new产生的对象
- 静态域:存放在对象中的用static定义的静态成员
- 常量池:存放常量
- 非RAM存储:硬盘等
栈:
创建一个变量即分配一个空间,当退出工作域时候,内存被自动释放。
堆:
存放new出的对象以及数组,有垃圾回收器管理。 栈中的变量指向了堆中的变量,这就是
Java指针
常量池:
指的是在编译期间被确定并保存在.class文件中的一些数据。除了包含代码中所定义的各种基本类型和对象的常量值(final),也还包含一些文本形式出现的符号引用,如:类和接口的全限定名,字段的名称和描述符,方法和名称的描述符。
虚拟机为每个被装载的类型维护一个常量池。对于String常量,他的值是在常量池中。JVM提供一个表来存储文字字符串值,不存引用。程序执行的时候,常量池会存储在方法区中,而不是堆中。
这里衍生出几个经常被问到的面试题:
String str = new String("abc");
String str2="abc";
这是创建字符串的两种方式,第一种是在堆中new一个对象。每次调用都会创建一个新的对象。而第二种是先在栈中创建一个String对象的引用地址。然后去常量池中寻找“abc”的存在与否,如果存在,则让那个引用指向“abc”,否则将“abc”放入常量池,并令引用指向“abc”。
再看:
- String s0="kvill";
- String s1="kvill";
- String s2="kv" + "ill";
- System.out.println( s0==s1 );
- System.out.println( s0==s2 );
输出结果会是什么呢?
答案是2个true。
首先我们要知道一个String常量只会有一个拷贝,s0和s1都是字符串常量,在编译期间就被确定,所以第一个是true
而kv与ill也都是字符串常量,在编译期间也被认为是字符串常量,所以s2也在常量池中kvill的一个引用,所以第二个也是true
再看看:
- String s0="kvill";
- String s1=new String("kvill");
- String s2="kv" + new String("ill");
- System.out.println( s0==s1 );
- System.out.println( s0==s2 );
- System.out.println( s1==s2 );
很多人可能会答错,正确答案是三个false
s0!=s1是肯定的,但是s0为什么!=s2呢?
因为s2的后半部分是new出来的,在运行期间才能被确定,因此合起来只能创建一个新对象在堆中,所以s0!=s2
String.intern()方法:当一个String实例调用intern时候,他会查找常量池中是否有相同Unicode值得字符串常量,有就返回其引用,若没就在常量池中创建一个相同的字符串并返回引用。
看例子:
- String s0= "kvill";
- String s1=new String("kvill");
- String s2=new String("kvill");
- System.out.println( s0==s1 );
- System.out.println( "**********" );
- s1.intern();
- s2=s2.intern(); //把常量池中"kvill"的引用赋给s2
- System.out.println( s0==s1);
- System.out.println( s0==s1.intern() );
- System.out.println( s0==s2 );
结果是:
false
false //虽然执行了s1.intern(),但它的返回值没有赋给s1
true //说明s1.intern()返回的是常量池中"kvill"的引用
true
false //虽然执行了s1.intern(),但它的返回值没有赋给s1
true //说明s1.intern()返回的是常量池中"kvill"的引用
true
还有几个常见例子:
- String a = "a1";
- String b = "a" + 1;
- System.out.println((a == b)); //result = true
- String a = "atrue";
- String b = "a" + "true";
- System.out.println((a == b)); //result = true
- String a = "a3.4";
- String b = "a" + 3.4;
- System.out.println((a == b)); //result = true
分析:JVM对于字符串常量的+号连接,在程序编译期,JVM就把常量字符串+连接优化为连接后的值,那“a”+1来说,两者都是在常量池中,因此编译器就确定为a1,所以结果是true
- String a = "ab";
- String bb = "b";
- String b = "a" + bb;
- System.out.println((a == b)); //result = false
分析:bb是一个地址的引用,因此无法再编译期间就确定,所以为false
- String a = "ab";
- final String bb = "b";
- String b = "a" + bb;
- System.out.println((a == b)); //result = true
bb被申明为常量,因此在编译期间就确定了,所以为true