Java的jvm上的内存位置的分配
浅析Java的jvm上的内存位置的分配
1.Java的内存区域简介
1>程序计数器:
一小块的内存空间,每个线程都有一个独立的计数器,线程私有;作用:作为当前线程代码行行号指示器,这个值可以选取下一条需要执行的字节码指令,例如分支,循环等,每创建一根线程会相应的产生一个程序计数器
2>栈
线程私有,用于存放局部变量,保存基本数据类型的值,操作数栈(保存着计算过程的中间结果),动态链接,方法入口和出口等信息;局部变量表中保存着函数的参数和局部变量,当调用结束以后,栈帧销毁,局部变量表也随之销毁 ,局部变量表可以复用过期的局部变量的槽位,栈帧弹栈有两种情况,return和异常,栈为先进后出的结构,有深度所以会溢出,上述所说的操作数栈和局部变量表是都是栈帧的组成部分,栈帧至少有三部分组成,操作数栈,局部变量表,帧数据区。
3>堆
线程共享;虚拟机所管理内存区域最大的一块地方,存放对象实例和数组,注意创建出来的对象只包含属于各自的成员变量(对象的属性),并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。
4>本方法地栈
为native服务(c/c++/fortran编写),行为和栈基本类似
5>方法区
线程共享,用于存放虚拟机加载的类信息,方法区中有常量池存放着常量(字面量),静态变量等数据
6>运行时常量池
方法区的一部分,用于存放编译期生成的各种字面量和符号引用(符号引用:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符)
7>直接内存
直接内存不是java规范中的内存,也不隶属于堆,大小不受堆大小的限制,直接内存是指向java堆外,直接向系统申请内存空间,通常情况下直接内存的效率会优于java堆,直接内存和java堆的总和依然受限于操作系统能给出的最大内存限制。在nio(见后续nio)中是通过堆中的DirectByteBuffer实现对直接内存进行访问,能在一些场景中显著的提高性能
2.实例1:
package StaticTest;
public class Test { //加载在方法区
public static int i0=10; //方法区
public final int i=100;//常量池
public static void main(String[] args) {
int i1=10; //栈
int i2=10;//栈
String str1="abc"; //栈指向常量池
String str2="abc"; //str2栈指向常量池
String str3=str1+str2;//栈指向堆
String str4="abcabc";栈指向常量池
String str5=new String("abc"); 栈指向堆
int i3=i1+i2; 栈
int i4=20; 栈
System.out.println(i0==i1);
System.out.println(i1==i2);
System.out.println(i3==i4);
System.out.println(str1==str2);
System.out.println(str3==str4);
System.out.println(str1==str5);
}
}true true true true false false
分析:首先会将类加载到方法区,然后放置一个class对象在堆区作为引用,main方法自身在方法区, i0在方法区等,基本数据类型i1,i2,以及与使用了常量池技术的字符串str1,str2,str4的引用在栈中,栈中的数据是可以共享的,所以i1,i2是指向同一个数字,对于栈中存放的基本类型数据,假如不存在那么就创建,假如存在那么就指向存在的地址,基本类型数据的运算也是在栈中进行(操作数栈),对于包装类(不论有没有new)与基本类型数据的==及各种运算,java会自动进行拆箱操作转换为相应的基本类型数据,指向相应的栈中,包装类(不论有没有new)与包装类(不论有没有new)的各种运算也会相应的转换为基本类型数据后进行相应的运算(==除外),new的实例在堆中,new只是创建各自的成员变量(不包含静态部分),不包含方法体,被final修饰的数据在常量池中,对于String和包装类都实现了常量池技术,String会先检查常量池中是否存在,假如不存在那么会创建,存在就指向存在的地址,String的加法是在堆中进行的,字符串的+底层是基于StringBuilder(见后续的java的字符处理)。在jdk6.0中String的常量池在方法区中,在jdk7.0中的String的常量池在堆中,综上所诉:new之后会创建各自的成员变量;而对于方法区中(这些是通过放入堆中的class对象来实现调用的)的静态成员变量及方法自身只会存在一份,对于常量池中的常量也只存在一份,且设定初始值后不能改变,对于Byte及小于-128~127之间的Integer以及Character的包装类以及String在没有new的前提下是直接指向常量池的同一个地址,new过之后会相应的进入堆中,对于其他的包装类,会相应的进入到堆中。常量池中的字面量和栈中的基本类型数据有区别,栈中的基本类型数据会相应的自动转换,常量池中的字面量不会自动转换,例如Double d1=123456d;后续要加d,不加d就会编译错误等
实例和对象的区别 :new或者反射出来的实例(newInstance等)叫实例,new出来或者反射出来的实例的引用叫对象;