一.内存分配区域
程序计数器
程序计数器,可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作就是通过改变程序计数器的值来选择下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都要依赖这个计数器来完成。
java虚拟机栈
java虚拟机栈也是线程私有的,它的生命周期与线程相同,虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链表,方法出口信息等。每个方法从调用直至执行完成的过程,就对应着一个栈帧在徐牛基栈中从入栈到出栈的过程。
其中我们所说的“栈”就是虚拟机栈,或者说是虚拟机栈中的局部变量表部分。
局部变量表存放了编译期可知的各种基本数据类型,对象引用,它不等于对象本身,可能是一指向对象地址的引用指针,
也可能代表对象的句柄或者其他与此对象相关的位置。
局部变量表
本地方法栈
本地方法栈与虚拟机的作用相似,不同之处在于虚拟机栈为虚拟机执行java方法(字节码)服务。
java堆
java堆是线程共享的一块区域,在虚拟机启动时创建。唯一的目的存放对象实例。这一点在java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
方法区
方法区与java堆一样,是各个线程共享的内存区域,他用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
运行时常量池
运行时常量池是方法区的一部分。用于存放编译期生成的各种字面量和符号引用。这部分内容将在类加载后存放到 方法区的运行时常量池中。
静态域:存放在对象中用static定义的静态成员
常量池:存放常量
二.java内存分配
-----基础数据是直接在栈空间飞陪
------方法的形式参数,直接在栈空间分配,当方法调用玩后从栈空间回收;
-------引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
------方法的应用参数,在栈空间分配一个地址空间,并指向堆空间的对象去,方法调用完后从栈空间回收;
----局部变量new出来时,在栈空间和堆空间分配,当局部变量生命周期结束后,栈空间立刻被回收,堆空间等待GC回收;
---数据集在栈空间分配数据名称,又在堆空间分配数据的实际大小。
------常量在DATA区域分配,this在对空间分配。
栈
三.String 常量池问题
String 常量池问题
(1) 字符串常量的"+"号连接,在编译期字符串常量的值就确定下来, 拿"a" + 1来说,编译器优化后在class中就已经是a1。
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
(2) 对于含有字符串引用的"+"连接,无法被编译器优化。
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false
由于引用的值在程序编译期是无法确定的,即"a" + bb,只有在运行期来动态分配并将连接后的新地址赋给b。
(3) 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝并存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true
(4) jvm对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b。
String a = "ab";
final String bb = getbb();
String b = "a" + bb;
System.out.println((a == b)); //result = false
private static string getbb() {
return "b";
}
(5) String 变量采用连接运算符(+)效率低下。
String s = "a" + "b" + "c"; 就等价于String s = "abc";
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c;
这个就不一样了,最终结果等于:
Stringbuffer temp = new Stringbuffer();
temp.append(a).append(b).append(c);
String s = temp.toString();
(6) Integer、Double等包装类和String有着同样的特性:不变类。
String str = "abc"的内部工作机制很有代表性,以Boolean为例,说明同样的问题。
不变类的属性一般定义为final,一旦构造完毕就不能再改变了。
Boolean对象只有有限的两种状态:true和false,将这两个Boolean对象定义为命名常量:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
这两个命名常量和字符串常量一样,在常数池中分配空间。 Boolean.TRUE是一个引用,Boolean.FALSE是一个引用,而"abc"也是一个引用!由于Boolean.TRUE是类变量(static)将静态地分配内存,所以需要很多Boolean对象时,并不需要用new表达式创建各个实例,完全可以共享这两个静态变量。其JDK中源代码是:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing)是JSE 5.0提供的新功能。 Boolean b1 = 5>3; 等价于Boolean b1 = Boolean.valueOf(5>3); //优于Boolean b1 = new Boolean (5>3);
static void foo(){
boolean isTrue = 5>3; //基本类型
Boolean b1 = Boolean.TRUE; //静态变量创建的对象
Boolean b2 = Boolean.valueOf(isTrue);//静态工厂
Boolean b3 = 5>3;//自动装箱(autoboxing)
System.out.println("b1 == b2 ?" +(b1 == b2));
System.out.println("b1 == b3 ?" +(b1 == b3));
Boolean b4 = new Boolean(isTrue);不宜使用
System.out.println("b1 == b4 ?" +(b1 == b4));//浪费内存、有创建实例的时间开销
} //这里b1、b2、b3指向同一个Boolean对象。
(7) 如果问你:String x ="abc";创建了几个对象?
准确的答案是:0或者1个。如果存在"abc",则变量x持有"abc"这个引用,而不创建任何对象。
如果问你:String str1 = new String("abc"); 创建了几个对象?
准确的答案是:1或者2个。(至少1个在heap中)
(8) 对于int a = 3; int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。