您好,欢迎访问三七文档
java内存机制1概述2内存分配2.1-----内存空间的分配2.2-----数组在内存中的运行机制2.3-----方法函数的传递机制3内存回收-----垃圾回收机制概述对于从事C和C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的皇帝,又是从事最基础工作的劳动——既拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任。内存分配(创建Java对象时)内存回收(回收Java对象时)Java内存管理当一个Java对象失去引用时,JVM的垃圾回收机制会动消除它们,并回收它们所占用的内存空间JVM会在堆内存中为每个对象分配空间2.1内存空间的分配栈内存堆内存Java内存存放一些基本类型的变量和对象的引用变量,每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程存放由new创建的对象和数组,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,为何有堆内存栈内存之分答:当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存也将自然销毁了,因此,所有在方法中定义的变量都是在栈内存中的;当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在合适的时候回收它。2.2数组在内存中的运行机制数组:是一种引用数据类型,数组引用变量只是一个引用,数组元素和数组变量在内存里是分开存放的。实际的数组元素被存储在堆(heap)内存中,数组引用变量被存储在栈(stack)内存中数组引用变量是访问堆内存中数组元素的唯一方式。下面将举例介绍数组在内存中的运行机制。当程序定义并初始化了a、b两个数组后,系统内存中实际上产生了4块内存区,其中栈内存中有两个引用变量:a和b;堆内存中也有两块内存区,分别用于存储a和b引用所指向的数组本身。此时计算机内存的存储示意如图所示:int[]a={5,7,20};int[]b=newint[4];执行b=a代码时,系统将会把a的值赋给b,a和b都是引用类型变量,存储的是地址。因此把a的值赋给b后,就是让b指向a所指向的地址。此时计算机内存的存储示意如图4.4所示:b=a;2.2.1基本类型数组的初始化对于基本类型数组而言,数组元素的值直接存储在对应的数组元素中,因此基本类型数组的初始化比较简单:程序直接先为数组分配内存空间,再将数组元素的值存入对应内存里.执行第一行代码时,在main方法中仅定义了一个iArr数组变量,它是一个引用类型的变量,并未指向任何有效的内存,没有真正指向实际的数组对象。此时内存中的存储如图1.4所示。int[]iArr;当执行静态初始化后,系统会根据程序员指定的数组元素来决定数组的长度。也就是数组所占用的内存空间被固定下来。此时指定了4个数组元素,系统将创建一个长度为4的数组对象,一旦该数组对象创建成功,该数组的长度将不可改变,程序只能改变数组元素的值。此时内存中的存储如图1.5所示iArr=newint[]{2,5,-12,20};2.2.2引用类型数组的初始化为了说明引用类型数组的运行过程,下面程序先定义一个Person类,然后定义了一Person[]数组,并动态初始化了该Person数组,再显式为数组的不同数组元素指定值。该程序代码如下:执行代码时,仅仅在栈内存中定义了一个名为students的引用变量,也就是一个指针,这个指针并未指向任何有效的内存区。此时内存中的存储如图1.6所示。Person[]students;执行时,该数组的2个数组元素都是引用,而且这2个引用并未指向任何有效的内存,故系统为数组元素分配默认的初始值null,即每个数组元素的值都是null.执行动态初始化后的存储如图1.7所示.students=newPerson[2];执行时,程序在堆内存中分别创建一个Person对象,并将Person对象分别赋给名为zhang和lee的引用变量;此时的zhang、lee这两个引用变量存储在main方法栈区中.而两个Person对象则存储在堆内存中。此时的内存存储如图1.8所示。Personzhang=newPerson();zhang.age=15;zhang.height=158;Personlee=newPerson();lee.age=16;lee.height=161;接着,执行两行代码,也就是让zhang和student[0]指向同一个Person对象,让lee和students[1]指向同一个Person对象。此时的内存存储如图1.9所示。students[0]=zhang;students[1]=lee从图1.9中可以看出:此时zhang和students[0]指向同一个内存区,而且它们都是引用类型变量,因此通过zhang和students[0]来访问Person实例的属性和方法的效果完全一样。不论修改students[0]所指向的Person实例的属性,还是修改zhang变量所指向的Person实例的属性,所修改的其实是同一个内存区,所以必然互相影响。同理,lee和students[1]也是引用到同一个Person对象,也有相同的效果。2.2.3多维数组定义二维的数组的语法:type[][]arrName;但它的实质还是一维数组,只是其数组元素也是引用,数组元素里保存的引用指向一维数组。执行将在栈内存中定义一个引用变量a,此时的堆内存中还未为这行代码分配任何存储区。对a数组执行初始化:这行代码让a变量指向一块长度为3的数组内存,这个长度为3的数组里的每个数组元素都是引用类型(数组类型),系统为这些数组元素分配默认初始值nullint[][]a;a=newint[3];因为a数组的元素必须是int[]数组,所以接下来的程序对a[0]元素执行初始化,也就是让图4.12右边堆内存的中第一个数组元素指向一个长度为2的int数组。因为程序采用动态初始化a[0]数组,因此系统将为a[0]的每个数组元素分配默认初始值0,当执行程序显式为a[0]数组的第二个元素赋值为6。此时在内存中的存储示意如图4.13所示:a[0]=newint[2];a[0][1]=6;2.3方法的参数传递机制Java里方法的参数传递方式只有一种:值传递。所谓值传递,就是将实际参数值的副本(复制品〉传入方法内,而参数本身不会受到任何影响。下面介绍值传递时内存是怎样运行的基本类型的参数传递:从上面运行结果来看,swap方法里a和b的值是9、6,交换结束,实参a和b的值依然是6、9。从这个运行结果可以看出,main方法里的变量a和b,并不是swap方法里的a和b。正如前面讲的:swap方法的a和b只是main方法里变量a和b的复制品运行上面程序,看到如下运行结果:程序从main方法开始执行,两个变量在内存中的存储示意如图所示:当程序执行swap方法时,系统分别为main方法和swap方法分配两块栈区,分别用于保存main方法和swap方法的局部变量并将main方法中的a、b变量作为参数值传入swap方法,也就是对swap方法的a、b形参进行了初始化inta=6;intb=9;swap(a,b);程序在swap方法交换a、b两个变量的值,即对图5.4中灰色覆盖区域的a、b变量进行交换,交换结束后swap方法中输出a、b变量的值,看到a的值为9,b的值为6,内存中的存储如图:main方法核区中a、b的值、并未有任何改变。程序改变的只是swap方法校中的a、b。这就是值传递的实质:当系统开始执行方法时,系统为形参执行初始化,就是把实参变量的值赋给方法的形参变量,方法里操作的并不是实际的实参变量。引用类型的参数传递:执行上面程序,看到如下运行结果:从上面运行结果来看,在swap方法里,a、b两个属性值被交换成功。不仅如此,main方法里swap方法执行结束后,a、b两个属性值也被交换了。这很容易造成一种错觉:调用swap方法时,传入swap方法的,就是dw对象本身,而不是它的复制品。但这只是一种错觉,下面还是依然结合示意图来说明程序的运行过程。程序从main方法开始执行,main方法开始创建了一个DataWrap对象,并定义了一个dw引用变量来指向DataWrap对象,此时堆内存中保存了对象本身,栈内存中保存了该对象的引用。接着程序通过引用来操作DataWrap对象,把该对象的a、b属性分别赋为6、9。此时系统内存中的存储示意如图5.6所示:DataWrapdw=newDataWrap();dw.a=6;dw.b=9;执行系统会分别开辟出main和swap两个栈区,分别用于存放main和swap方法的局部变量。调用swap方法时,dw变量作为实参,传入swap方法。值得指出的是,main方法里的dw是一个引用(也就是一个指针),它保存的DataWrap对象的地址值,即也引用到堆内存中的DataWrap对象,图5.7显示了dw传入swap方法后的存储示意:swap(dw);从图5.7来看,这种参数传递方式是不折不扣的值传递方式,系统复制了dw的副本传入swap方法,当程序在swap方法中操作dw形参时,实际操作的还是堆内存中的DataWrap对象。此时,不管是操作main方法里的dw变量,还是操作swap方法里的dw参数,它们操作的是同一个对象。因此,当swap方法中交换dw参数所引用DataWrap对象的a、b属性值后,我们看到main方法中dw变量所引用DataWrap对象的a、b属性值也被交换了。3垃圾回收机制垃圾回收(GarbageCollection,也被称为GC):与C/C++程序不同,Java语言不需要程序员直接控制内在回收,Java程序的内存分配和回收都是由JRE在后台自动进行的。JRE会负责回收那些不再使用的内存,这种机制被称为垃圾回收机制。垃圾回收机制主要完成下面两件事情:•跟踪并监控每个Java对象,当某个对象处于没有任何引用变量引用它的状态时,虚拟机回收该对象所占用的内存;•清理内存分配、回收过程中产生的内存碎片.Obj2此时可为回收对象通常垃圾回收有如下的几个特点:垃圾回收机制的工作目标是回收无用对象的内存空问,这些内存空间都是JVM堆内存里的内存空间,垃圾回收只能回收内存资源,对其他物理资源,如数据库连接、磁盘10等资源则无能为力。为了更快地让垃圾回收机制回收那些不再使用的对象,可以通过将该对象的引用变量设置为null,通过这种方式暗示垃圾回收机制可以回收该对象。垃圾回收发生的不可预知性:由于不同JVM采用了不同的垃圾回收机制和不同的垃圾回收算法,因此它有可能是定时发生,有可能是当CPU空闲时发生,也有可能是和原始的垃圾回收一样,等到内存消耗出现极限时发生,这些和垃圾回收实现机制的选择和具体的设置都有关系。垃圾回收的精确性主要包括两个方面:一、垃圾回收机制能够精确标记活着的对象;二、垃圾回收器能够精确地定位对象之间的引用关系。前者是完全地回收所有废弃对象的前提,否则就可能造成内存泄漏。而后者则是实现归并和复制等算法的必要条件,通过这种引用关系,可以保证所有对象都能被可靠地回收,所有对象都能够被重新分配,从而有效地减少内存碎片的产生。现在的JVM有多种不同的垃圾回收实现,每种回收机制因其算法差异可能其表现各异,有的当垃圾回收开始时就停止应用程序的运行,有的当垃圾回收运行时,同时允许应用程序的线程运行,还有的在同一时间垃圾回收多线程运行。当我们编写Java程序时,一个基本原则是:对于不再需要的对象,不要再引用它们。如果我们保持了对这些对象的引用,垃圾回收机
本文标题:java内存机制
链接地址:https://www.777doc.com/doc-2411087 .html