首先java程序主要运行在java虚拟机上即JVM,JVM课理解为java程序与各个操作系统的桥梁,实现java程序的平台无关性。而java中的内存分配的一系列过程都发生在JVM中。
在一个java程序运行的过程中主要涉及到的内存区域有:
--寄存器:JVM定义寄存器后,便可以从中获取信息而不需要对栈或者是内存进行访问,提高了运行速度。
JVM中定义了4个常用的寄存器:
--栈:在函数中定义的一些基本类型的变量和对象的引用变量(局部变量)都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。
--堆:堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。
--常量池:java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
内存图解:
--程序计数器:jvm执行程序的流水线,存放一些跳转指令。(不是太懂以后研究)
--虚拟机栈:JVM执行java代码时所用的栈;
--本地方法栈:JVM调用操作系统方法使用的栈
--方法区:存放了一些常量、静态变量、类信息等(可以理解成class文件在内存中的存放位置)。
--堆:JVM执行代码时使用的堆。
实例:
其中:在创建Test实例后在栈中分配一块内存存放一个指向堆中的对象的指针346387;
创建一个int基本类型变量,直接在栈中存放a的值;
创建一个Date实例d2,d3(局部变量)后在栈中分别分配一块内存块存放指向对中对象的指针346345,756433,对象实例化时调用构造方法,对象有了初始值。
执行change1以a为参数,JVM检测到b为局部变量因此将a的值赋值给b;
将100赋值给b;
change1执行完毕后将释放b所占用的栈空间。
当test调用change2方法时d2为参数,JVM检测c为局部变量立即在栈中分配一块空间,由于是引用变量将d2中的指针赋值给c,所以d2和c指向的是同一个对象。
d2和c之间的传递的是指针。
change2方法中又实例化了一个新的Date对象,将在堆中分配一块内存区域存放new出来的对象,并将相应的指针赋 值给c,此时c中的指针不在指向d2所指向的对象了,
但是这对d2所指向的对象没有改变。
change2方法执行完毕,立即释放栈中c所占内存资源,堆中内存等待垃圾回收器自动回收。
test调用change3方法,参数对象为d3,JVM检测到为引用变量后在栈中分配一块内存放d并将d3中的指针赋值给d,此时d和d3指向的对象为同一个对象。
change3方法中执行实例d的setDate方法即为d3的setDate方法将,调用此方法将会影响到d3,因为他们指向的是同一个对象。
change3执行完毕,立即释放栈中引用变量内存资源。
两种类型变量:基本类型,引用类型。
作为局部变量时都放在栈中,基本类型直接将值存放在栈中,引用变量将指向对象的指针存放在栈中,对象存放在堆中。
作为参数是基本类型直接传值;引用类型传指针。
相关知识:
--实例和对象:Date b = new Date();其中b为一个实例不是一个对象,实例在栈中对象在堆中,我们在操作实例的时候其实是通过实例中的指针间接的操作指针指向的对象的,多个实例可以指向同一个对象。
--栈中资源和堆中资源释放并不是同步的,方法结束栈中的局部变量立即销毁,而堆中的资源不会立即销毁有可能还有其他栈中实例指向这个对象,若栈中没有实例指向这个对象也不会立即被销毁需要等待垃圾回收器处理。
--每一个应用程序都对应唯一JVM实例,JVM实例中的内存区域互不影响,并且这些内存区域都是线程共享的。
----关于常量池
--基本类型和包装类型:
-基本类型:byte,short,char,int,long,float,double,boolean;
-包装类型:Byte,Short,Character,Integer,Long,Float,Double,Boolean;
基本类型体现在程序中为普通变量,包装类型为类体现在程序中为引用变量,则两者在内存中存储位置也不同,基本类型存在栈中,包装类型存在堆中。
其中两种浮点类型包装类没有实现常量池技术,另外String类也实现了常量池技术。
代码块:
1 String s1 = "Hello"; 2 String s2 = "Hello"; 3 String s3 = "Hel" + "lo"; 4 String s4 = "Hel" + new String("lo"); 5 String s5 = new String("Hello"); 6 String s6 = s5.intern(); 7 String s7 = "H"; 8 String s8 = "ello"; 9 String s9 = s7 + s8; 10 11 System.out.println(s1 == s2); // true 12 System.out.println(s1 == s3); // true 13 System.out.println(s1 == s4); // false 14 System.out.println(s1 == s9); // false 15 System.out.println(s4 == s5); // false 16 System.out.println(s1 == s6); // true首先在String中==为比较引用字符串的地址,使用String.equals()比较内容。
其中s1==s2为true,s1与s2直接用字面量赋值,在编译期直接将字面量存入class文件的常量池中,实现重复使用,程序运行载入常量池后s1和s2为同一地址,所以相同为true。
s1==s3,中s3会在比较前进行预编译,即比较是s3 = "Hello";所以s1与s3也为同一地址结果为true;
s1==s4,这里s4中new String("lo");不是已知的字面量为不可预料的字符串所以不会进行预编译,要等到运行的时候才能可以得到结果。所以两者地址不同,
如下图为s4储存过程:
s1==s9,虽然s7和s8是字面量在常量池中但是拼接为s9经过编译器编译运行后生成新的字符串,在对中的地址不确定,则与s1中的地址不同。
s4==s5,这两者都在堆中但是两者对象不是同一个所以地址不同。
s1==s6,s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址;
实际上还有整型常量池、浮点型常量池等等,但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,只有这个范围的数字可以用到常量池。
参考文章:http://www.cnblogs.com/iyangyuan/p/4631696.html
http://blog.csdn.net/shimiso/article/details/8595564