Java 内存消耗与堆栈

  • Post author:
  • Post category:java



1.

java

是如何管理内存的





Java

的内存管理就是对象的分配和释放问题。







分配


:内存的分配是由程序完成的,通过关键字

new


为每个对象申请内存空间


(


基本类型除外


)


,所有的对象都在堆



(Heap)


中分配空间。














释放


:对象的释放是由垃圾回收机制决定和执行的。










这样做加重了

JVM


的工作。因为,


GC


为了能够正确释放对象,


GC


必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,


GC


都需要进行监控。





2.


什么叫


java


的内存泄露






Java


中,内存泄漏就是存在一些被分配的对象,这些对象有两个特点:1.这些对象是可达的,即在有向图中,存在通路可以与其相连(也就是说仍存在该内存对象的引用);2.这些对象是无用的,程序以后不会再使用这些对象。



如果对象满足这两个条件,这些对象就可以判定为


Java


中的内存泄漏,这些对象不会被


GC


所回收,然而它却占用内存。


3.

JVM

的内存区域组成



java

内存分两种:栈内存、堆内存




3.1.


在函数中定义的基本类型、对象的引用类型变量都在函数的栈内存中分配;



3.2.




new


创建的对象、数组、对象的实例变量放在堆内存




在函数(代码块)中定义一个变量时,

java


在栈中为这个变量分配内存空间,当超过变量的作用域后

java


会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由


java


虚拟机的自动垃圾回收器来管理







4

.堆和栈的优缺点








4.1堆的优势是 可以动态分配内存大小,生存期不必事先告诉编译器,因为它是在运行时动态分配内存的。









缺点是 要在运行时动态分配内存,存取速度较慢.










4.2栈的优势是 存取速度比堆要快,仅次于直接位于

CPU


中的寄存器。










栈数据可以共享








缺点是 栈中的数据大小与生存期必须是确定的,缺乏灵活性。





5.



Java

中数据在内存中是如何存储的





5.1


基本数据类型




基本数据类型

8


种,



int

a

=

3


;这里的


a


是一个指向


int


类型的引用,指向


3


这个字面值。这些字面值的数据,由于大小可知,生存期可知


(


这些字面值定义在某个程序块里面,程序块退出后,字段值就消失了


)


,出于追求速度的原因,就存在于栈中。








由于

栈有一个很重要的特性,存在栈中的数据可以共享。于是我们同时定义:


int

a=3;


int

b

=3;



编译器先处理


int

a

=

3


;首先它会在栈中创建一个变量为


a


的引用,然后查找有没有字面值为


3


的地址,没找到,就开辟一个存放


3


这个字面值的地址,然后将


a


指向


3


的地址。

接着处理

int

b

=

3


;在创建完


b


这个引用变量后,由于在栈中已经有


3


这个字面值,便将


b


直接指向


3


的地址。

这样,就出现了

a





b


同时均指向


3


的情况。























定义完


a





b


的值后,再令


a

=

4


;那么,


b


不会等于


4


,还是等于


3


。在编译器内部,遇到时,它就会重新搜索栈中是否有


4


的字面值,如果没有,重新开辟地址存放


4


的值;如果已经有了,则直接将


a


指向这个地址。因此


a


值的改变不会影响到


b


的值。


















5.2


对象




















Java


中,创建一个对象包括对象的声明和实例化。
















下面用一个例题来说明对象的内存模型。













假设类


Rectangle


定义如下:







public



class


Rectangle

{







double



width

;






double



height

;






public


Rectangle(

double


w,

double


h){








w

=


width

;







h

=


height

;






}






}







1.声明对象时的内存模型








Rectangle


rect


,声明一个对象


rect


,在栈内存为对象的引用变量


rect


分配内存空间。














Rectangle


的值为空,


rect


是一个空对象。空对象不能使用,因为它还没有引用任何





实体


















2.


对象实例化时的内存模型








当执行

rect=new

Rectangle(3,5)


;时,会做两件事:















在堆内存中为类的成员变量


width,height


分配内存,并将其初始化为各数据类型的默认值;














接着进行显式初始化(类定义时的初始化值,调用构造方法












为成员变量赋值。


返回堆内存中对象的引用(相当于首地址)给引用变量


rect,


以后就可以通过


rect


来引用堆内存中的对象了。
















3.



创建多个不同的对象实例













一个类通过使用


new


运算符创建多个不同的对象实例,这些对象实例将在堆中被分配不同的内存空间,改变其中一个对象的状态不会影响其他对象的状态。














Rectangle

r1=


new


Rectangle(

3

,

5

);









Rectangle

r2=


new


Rectangle(

4

,

6

);









此时,将在堆内存中分别为两个对象的成员变量

width





height


分配内存空间,两个对象在堆内存中占据的空间是互不相同的。如果有:










Rectangle

r1=


new


Rectangle(

3

,

5

);








Rectangle

r2=r1;







则在堆内存中只创建了一个对象实例,在栈内存中创建了两个对象引用,两个对象引用同时指向一个对象实例。













4.

包装类

基本类型的定义在栈中,包装类创建对象,就和普通对象一样了。











5.


String







是一个特殊的包装类数据。可以用用以下两种方式创建:


String

str

=


new


String(“abc”)




String

str

=

“abc”;












第一种创建方式,和普通对象的的创建过程一样;






第二种创建方式,

Java


内部将此语句转化为以下几个步骤:








(1)


先定义一个名为


str


的对


String


类的对象引用变量:


String

str











(2)


在栈中查找有没有存放值为


“abc”


的地址,如果没有,则开辟一个存放字面值为


“abc”的





地址,接着创建一个新的

String


类的对象


o


,并将


o


的字符串值指向这个地址,而且在栈





地址旁边记下这个引用的对象

o


。如果已经有了值为


“abc”


的地址,则查找对象


o


,并







o


的地址。
























(3)





str


指向对象


o


的地址。








值得注意的是,一般

String


类中字符串值都是直接存值的。但像


String

str

=

“abc”


;这种







合下,其字符串值却是保存了一个指向存在栈中数据的引用。





为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。






String

str1=”abc”










String

str2=”abc”









System

.out.println(s1==s2)




//true








注意,这里并不用

str1.equals(str2)


;的方式,因为这将比较两个字符串的值是否相等。


==


号,根据


JDK


的说明,只有在两个引用都指向了同一个对象时才返回真值。

















我们再接着看以下的代码。






String

str1=


new


String(

“abc”

)










String

str2=

“abc”








System

.out.println(str1==str2)





//false







创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。






以上两段代码说明,只要是用


new()


来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。












6.


数组















当定义一个数组,


int

x[]


;或


int

[]x


;时,在栈内存中创建一个数组引用,通过该引用(即数组名)来引用数组。


x=new

int[3]


;将在堆内存中分配


3


个保存


int


型数据的空间,堆内存的首地址放到栈内存中,每个数组元素被初始化为


0









7.



静态变量






静态表示的是内存的共享,就是它的每一个实例都指向同一个内存地址,

把它改了,它就不会变成原样,你把它清理了,它就不会回来了。







static


的修饰的变量和方法,实际上是指定了这些变量和方法在内存中的





固定位置








static

storage


,可以理解为所有实例对象共有的内存空间。


静态变量与方法是在什么时候初始化的呢?对于两种不同的类属性,


static


属性与


instance


属性,初始化的时机是不同的。


instance


属性在创建实例的时候初始化,


static


属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。