Java中的内存分析
内存分区
栈空间(stack)
- 由系统自动分配,遵循后进先出的原则,用于存放局部变量。
- 每个线程私有,不能实现线程间的共享。
- 速度快!栈是一个连续的内存空间。
堆空间(heap)
- 用于存放new出的对象,或者说是类的实例。
- 堆是一个不连续的内存空间,分配灵活,速度慢。
方法区(method)
- 在堆空间内。
- 被所有线程共享。
- 用来存放程序中永远不变或唯一的内容,如:①类的代码信息;②静态变量和方法;③常量池(字符串敞亮等,具有共享机制)。
常量池
JVM 为每个已加载的类维护一个常量池,常量池就是这个类用到的常量的一个有序集合。
包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用。
池中的数据和数组一样通过索引访问。
由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。
常量池存在于方法区(Method Area)。
实例分析
创建两个实体类和测试类
- 实体类 Student、Computer
package cn.share.vo; |
package cn.share.vo; |
- 测试类 Test
package cn.share; |
-
运行结果

代码分析
程序的入口是main(),因而从main方法从上到下、从左到右进行分析。
Student stu = new Student();
① 首先,Java虚拟机(JVM)去方法区寻找是否有 Test 类的代码信息,如果存在,直接调用。如果没有,通过类加载器(ClassLoader)把 .class 字节码加载到内存中,并把静态变量和方法、常量池加载(“ xiaoming ”、” Hasse ")
② 走到 Student,以同样的逻辑对 Student 类进行加载;静态成员;常量池(“studying”)。
③ 走到 stu,stu 在 main 方法内部,因而是局部变量,存放在栈空间中。
④ 走到 new Student,new 出的对象(实例),存放在堆空间中,以方法区的类信息为模板创建实例。
⑤ 赋值操作,把 new Student 的地址告诉 stu 变量,stu 通过四字节的地址(十六进制),引用该实例。

stu.setName(“xiaoming”);
⑥ stu通过引用new Student实例的name属性,该name属性通过地址指向常量池的"xiaoming"常量
stu.setAge(10);
⑦ stu实例的 age 属性是基本数据类型,基本数据类型直接赋值。
stu.study();
⑧ 调用实例的方法时,并不会在实例对象中生成一个新的方法,而是通过地址指向方法区中类信息的方法。
⑥⑦⑧的过程如下图:

Computer c = new Computer();
同stu变量的生成过程。
c.setBrand(“Hasse”);
同 stu.setName(“xiaoming”); 过程
stu.setComputer©;
⑨ 把 c 对象对 Computer 实例的引用赋值给 Student 实例的 computer 属性。亦即:该 Student 实例的computer 属性指向该 Computer 类的实例。
如下图:

拓展
改变brand的地址指向
重新将 Computer 实例的 brand 属性指向"Dell"常量,那 stu.computer.brand 指向谁呢?Dell 还是Hasse?
c.setBrand("Dell"); |
根据刚才的分析可知:
stu 通过地址引用 Student 实例,而该实例的 computer 的指向和 c 的指向是同一个 Computer 实例,因而改变该 Computer 实例的 brand 属性的指向,两者都会改变。
举个例子:
访问小明,和访问小明的儿子的爸爸,实质上访问的是同一个对象:小明。
因而,最终如上图测试结果是 true。
理解字符串常量及常量池
下面我们添加新的代码,如下:
String str = "Dell"; |
根据常量池具有共享性,可知并不会生成新的常量"Dell",而是会把 str 通过地址指向原来的 "Dell",因而结果是 true。






