一.Java的内存划分
Java的内存就像一间房子的划分一样,分为厨房,客厅,卧室。每个划分区域各司其职。Java的内存被划分为五个部分。
1.栈(Stack)
- 存放的都是方法中的局部变量。
- 方法的运行一定是在栈中的。
- 局部变量:方法的参数,或者是方法{}内部的变量。
- 局部变量的特点:有作用域,一旦超出作用域,就立刻从栈中消失。
2.堆(Heap)
- 凡是new出来的东西,都存在于堆中。
- 堆内存中的东西都有一个16进制的地址值。
- 堆内存里存放的数据都有默认的值;
—整数的默认值为0
—浮点数的默认值为0.0
—字符的默认值“\u0000”
—布尔的默认值false
—引用类型默认值为null
3.方法区(Method Area)
- 存储 .class 相关的方法,包含方法的信息。
- 有一个静态区存放静态数据
4.方法区(Native Method Stack)
- 与操作系统相关的信息
5.寄存器(pc Register)
- 与cpu相关的信息
二.对象有关内存图
只有一个对象的内存图
1 从方法区开始,存储 .class 相关的成员变量,和成员方法,如图中的 Phone.class,Demo01PhoneOne.class 中的变量。
2 main方法进栈,在栈中为 main方法开辟一个区域。
3进入 main方法中.
4 mian方法中第一句 Phone one = new Phone();
在main的区域中创建一个局部变量 Phone one,此变量保存的是堆中new Phone()的地址值。在堆中创建一个Phone对象,给其成员变量附上初始值,成员方法保存的是方法区中对应成员方法的地址值(0x333)如绿线。最后new Phone()也有一个地址值,我们把它赋值给Phone one 如红线。
5 赋值;one.brand = “苹果”,one.price = “8388.0”, one.color = “黑色”。就是通过 phone one 保存的地址找到堆中的 phone 对象,再找到他的属性,修改其中的值。也图中红色线条的路径。
6 方法执行 ; one.call(“乔布斯”),one.sendMessage()两个方法。
7 先执行one.call(“乔布斯”),先通过红线找到堆中的 new Phone(),在堆中对象 new Phone 中找到成员方法所保存的地址值,通过此地址值在方法区中找到对应的方法存储区域(也就是绿色框框),在此区域中找到 call(String who)方法,也就是图中绿色线条的路径。。 call(String who)方法想要运行必须入栈,call方法入栈之后,也给他划分一个区域,然后执行方法:“给谁打电话”。参数 who是乔布斯,传递给了栈中将要运行的方法,如图中蓝线所示路径。
8 call方法运行完成,就要出栈,在这里我们用一个差号表示出栈。
9 one.sendMessage() 方法的运行过程同call(String who)方法。
10 main中所有的语句都运行完成了, main方法也要出栈。
两个对象使用同一个方法的内存图
这里没什么好说的,new出来的对象是不同的,但堆中两个new出来的对象成员方法所存的地址都是相同的,都是方法区中的同一个,如绿色线所示。
两个引用指向同一个对象的内存图
代码变化的地方已经用红圈标识出来了。就是把one中保存对象的地址赋值给two。即one个two共用一个堆中的new Phone对象,如红线所示。如果 “只有一个对象的内存图” 懂了的话,这个应该也不难。
使用对象类型作为方法参数的内存图
method(one)方法执行前的内存图流程我就不在赘述了,直接从method(one)开始。首先在方法区中找到method方法 ,然后压入栈中。进栈之后要开辟一个内存给它使用。这个内存空间是怎么来的?就是根据method(one)调用而来的,如蓝线所示。
one怎么作为参数传入到method方法中的呢?
首先Phone one存储存储的是一个地址值(0x666)。
method(one)中的one也就是地址值(0x666)。如【1】线路。
然后param 接收one存储的地址值(0x666)。如【2】线路
param.brand,也就是0x666.brand。如【3】线路。
通过0x666能找到堆中的对象,也就能找到对象中的brand成员变量。
总结:当一个对象作为参数,传递到方法当中去时,实际上传递进去的是对象的地址值。
使用对象类型作为方法返回值的内存图
完整的流程:
1 方法区存 .class 的相关信息(成员变量,成员方法),代码中有两个 .class 文件。
2 main方法入栈,开辟内存空间。
3 Phone two = getPhone();调用了getPhone();方法,看方法区中有没有,有的话,通过蓝线调用入栈。开辟内存空间
4 Phone one = new Phone();
在getPhone的工作空间中,创建一个成员变量Phone one。在堆中创建一个new Phone();给其中的成员变量赋初始值。给成员变量赋上 方法区 中成员方法的地址值,如绿线。new Phone()也有一个地址值,在栈中我们用 Phone one 保存。
5 修改one中成员变量的值,这个操作不用多说了,通过地址值找到堆中对象,再去找此对象的成员变量,修改即可。
6 return one ; 也就是返回one 中所存的地址值。
7 main的内存空间中,成员变量Phone two接收到 返回的地址值。two.brand也就变成了0x666.brand。即可访问到堆中对象的成员方法。
总结:当使用一个对象类型作为方法的返回值时,返回值其实就是对象的地址值。