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。