在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该做用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间能够当即被另做他用。 面试
堆内存用来存放由new建立的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还能够 在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就至关因而为数组或对象起的一个名称,之后就能够在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就至关因而为数组或者对象起的一个名称。数组
常量池指的是在编译期被肯定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各类基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,好比: app
虚拟机必须为每一个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和 floating point常量)和对其余类型,字段和方法的符号引用。函数
对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引 用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。优化
在程序执行的时候,常量池会储存在Method Area,而不是堆中。spa
Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象经过new、newarray、 anewarray和multianewarray等指令创建,它们不须要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优点是能够动态地分配内存大小,生存期也没必要事先告诉编译器,由于它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些再也不使用的数据。但缺点是,因为要在运行时动态 分配内存,存取速度较慢。 code
栈的优点是,存取速度比堆要快,仅次于寄存器,栈数据能够共享。但缺点是,存在栈中的数据大小与生存期必须是肯定的,缺少灵活性。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象句柄(引用)。对象
对于字符串,其对象的引用都是存储在栈中的,若是是编译期已经建立好(直接用双引号定义的)的就存储在常量池中,若是是运行期(new出来的)才能肯定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。blog
这里咱们主要关心栈,堆和常量池,对于栈和常量池中的对象能够共享,对于堆中的对象不能够共享。栈中的数据大小和生命周期是能够肯定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,所以大小和生命周期不须要肯定,具备很大的灵活性。 接口
String s1 = "china"; String s2 = "china"; String s3 = "china"; String ss1 = new String("china"); String ss2 = new String("china"); String ss3 = new String("china");
这里解释一下黄色这3个箭头,对于经过new产生一个字符串(假设为“china”)时,会先去常量池中查找是否已经有了“china”对象,若是没有则在常量池中建立一个此字符串对象,而后堆中再建立一个常量池中此”china”对象的拷贝对象。
这也就是有道面试题:Strings=newString(“xyz”);产生几个对象?一个或两个,若是常量池中原来没有”xyz”,就是两个。
存在于.class文件中的常量池,在运行期被JVM装载,而且能够扩充。String的 intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java 查找常量池中是否有相同Unicode的字符串常量,若是有,则返回其的引用,若是没有,则在常量池中增长一个Unicode等于str的字符串并返回它的引用
String s0= "kvill"; String s1=new String("kvill"); String s2=new String("kvill"); System.out.println( s0==s1 ); 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
true
true
String常量池问题的几个例子:
【1】 String a = "ab"; String bb = "b"; String b = "a" + bb; System.out.println((a == b)); //result = false 【2】 String a = "ab"; final String bb = "b"; String b = "a" + bb; System.out.println((a == b)); //result = true 【3】 String a = "ab"; final String bb = getBB(); String b = "a" + bb; System.out.println((a == b)); //result = false private static String getBB() { return "b"; }
分析:
【1】中,JVM对于字符串引用,因为在字符串的"+"链接中,有字符串引用存在,而引用的值在程序编译期是没法肯定的,即"a" + bb没法被编译器优化,只有在程序运行期来动态分配并将链接后的新地址赋给b。因此上面程序的结果也就为false。
【2】和【1】中惟一不一样的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到本身的常量池中或嵌入到它的字节码流中。因此此时的"a" + bb和"a" + "b"效果是同样的。故上面程序的结果为true。
【3】JVM对于字符串引用bb,它的值在编译期没法肯定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态链接并分配地址为b,故上面程序的结果为false。
结论:
字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据建立方式不一样定(常量池和堆).有的是编译期就已经建立好,存放在字符串常量池中,而有的是运行时才被建立使用new关键字,存放在堆中。
对于基础类型的变量和常量,变量和引用存储在栈中,常量存储在常量池中。
int i1 = 9; int i2 = 9; int i3 = 9; final int INT1 = 9; final int INT2 = 9; final int INT3 = 9;
编译器先处理int i1 = 9;首先它会在栈中建立一个变量为i1的引用,而后查找栈中是否有9这个值,若是没找到,就将9存放进来,而后将i1指向9。接着处理int i2 = 9;在建立完i2的引用变量后,由于在栈中已经有9这个值,便将i2直接指向9。这样,就出现了i1与i2同时均指向9的状况。最后i3也指向这个9。
对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。 形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。 成员变量存储在堆中的对象里面,由垃圾回收器负责回收
class BirthDate { private int day; private int month; private int year; public BirthDate(int d, int m, int y) { day = d; month = m; year = y; } // 省略get,set方法……… } public class Test { public static void main(String args[]) { int date = 9; Test test = new Test(); test.change(date); BirthDate d1 = new BirthDate(7, 7, 1970); } public void change(int i) { i = 1234; } }
对于以上这段代码,date为局部变量,i,d,m,y都是形参为局部变量,day,month,year为成员变量。下面分析一下代码执行时候的变化: