引言
最近学习Java基础时,老师讲到面向对象这块知识中,内存时非常基础的一部分,但是也是非常非常重要的一部分。当你明白了代码再内存中时如何变化分配的,对于代码的书写和理解都会事半功倍。第一遍其实我是没有看太懂的,第二次看,有了不一样的理解。温故而知新!
基本概念?
每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈。进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享。
Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量。虽然Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也就是说在建立一个对象时在堆和栈中都分配内存,在堆中分配的内存实际存放这个被创建的对象的本身,而在栈中分配的内存只是存放指向这个堆对象的引用而已。局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。
栈:一般来说,基本数据类型直接在栈中分配空间,局部变量(在方法代码段中定义的变量)也在栈中直接分配空间,当局部变量所在方法执行完成之后该空间便立刻被JVM回收,还有一种是引用数据类型,即我们通常所说的需要用关键字new创建出来的对象所对应的引用也是在栈空间中,此时,JVM在栈空间中给对象引用分配了一个地址空间(相当于一个门牌号,通过这个门牌号就可以找到你家),在堆空间中给该引用的对象分配一个空间,栈空间中的地址引用指向了堆空间中的对象区(通过门牌号找住址);
堆:一般用来存放用关键字new出来的数据。
在Java语言里堆(heap)和栈(stack)里的区别 ?:
-
栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
-
栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
接下来,请跟我随通过一段程序来直观感受一下程序运行时的内存分配及其变化情况。
举例分析?
举例 请看代码?
Public class Test {
public static void main (String args[] {
Test test = new Test ();
int date = 9;
BirthDate d1 = new BirthDate (7,7,1970);
BirthDate d2 = new BirthDate (1,1,2000);
test.Change1 (date);
test.Change2 (d1);
test.Change3 (d2);
System.out.println ( "date = " + date);
d1.display ();
d2.display();
}
public void change1 (int i) {i = 1234;}
public void change2 (BirthDate b) {
b = new BirthDate (22,2,2019);
}
public void change3 (BirthDate b) {
b.setDay (22);
}
}
(以下是我拙劣的表演?)
代码内容大概有两部分,new Test出现,看到new一个对象,就要想到栈和堆的开辟。接着,int date=9,这个直接给int类型的date变量赋值了,这怎么在内存中体现呢❓
?Java中的四类八种属于基本的数据类型,这四类八种在内存中只占有栈内的一块内存就OK。除了这四类八种,剩下的我们都叫做“引用类型”,引用类型会占用两块内存,栈一块,堆一块。如图。
分析 下就是内存分配及其变化情况?,它对应了每一句代码执行时,内存时如何分配的。
首先,我们看内存的分布,date
在栈中有一块空间放着数据9
。接下来的两句又是两个new
新对象的代码,再往下走,看到test.change1(date)
,看起来是调用了Test
类中的方法了,找到这个方法,发现change1
方法有参数i
,正好和date
对应,方法中把1234
赋值给了i
.所以之前存放date
数据9
的栈中内存块有了一个变量名i
做标记,调用过change1
方法后,数据9
就变为了1234
。因为i
为局部变量,方法调用过后,它就在栈里消失了。
再往下走,两个局部变量d1,d2
出现,千万别忘记给他们俩在栈上开出两块空间存放它们各自的地址,change2
方法和前面的new
情况类似,只不过在它调用结束后,栈上的局部变量立即消失,堆里new
出来的那个对象失去了引用指向,不久后就会有神奇的Java垃圾收集器带走它。
我们来看看change3
方法,change3
调用了BirthDate
对象内部的方法,改变了Day
的数据为22
。
(注:虚线部分是会消失的凹)
我有话说?
通过上面的内存分析,不知道你有没有发现一个小秘密,局部变量的确会在栈内存有一席之地,不过,执行完它对应的方法它就消失掉了,因他而生的堆中的对象也会在不久后被垃圾收集器带走。但是,像change3
这样调用对象内部方法,对堆的改变是永久性的,直到这个main
方法执行结束。