在Java继承关系中,内存分配与类的加载、对象创建过程紧密相关,核心逻辑如下:
一、类加载阶段的内存布局
1. 静态区域分配(方法区)
- 父类和子类的静态变量( static )、静态代码块、类元数据(Class对象) 会被加载到JVM的方法区(JDK8后为元空间)。
- 顺序:先加载父类静态成员,再加载子类静态成员。
- 示例:
class Parent {
static { System.out.println("父类静态代码块"); }
static int staticVar = 10;
}
class Child extends Parent {
static { System.out.println("子类静态代码块"); }
static int staticVar = 20;
}
当首次使用 Child 类时,先加载父类 Parent 的静态成员,再加载子类 Child 的静态成员。
二、对象创建时的内存分配(堆内存)
1. 对象内存布局
- 子类对象在堆中分配的内存包含:
- 父类非静态成员(属性、方法引用)。
- 子类自身非静态成员。
- 本质:子类对象是“父类对象的扩展”,父类成员作为子类对象的一部分存在。
2. 构造函数执行与内存初始化
- 执行顺序:
1. 父类构造函数优先执行(默认调用无参构造 super() ),为父类非静态成员分配内存并初始化。
2. 再执行子类构造函数,为子类非静态成员分配内存并初始化。
- 示例:
class Parent {
int parentField = 10;
public Parent() {
System.out.println("父类构造函数,parentField = " + parentField);
}
}
class Child extends Parent {
int childField = 20;
public Child() {
System.out.println("子类构造函数,childField = " + childField);
}
}
创建 Child 对象时,先初始化父类的 parentField 为10,再初始化子类的 childField 为20。
三、引用类型与内存指向
1. 向上转型时的内存指向
- 当用父类引用指向子类对象(如 Parent p = new Child(); ):
- 引用变量 p 在栈中存储子类对象的地址。
- JVM通过对象头的类型信息确定实际对象类型(子类 Child ),调用方法时按子类实现执行(多态)。
2. 内存占用示例
- 假设父类 Parent 有属性 a 和 b ,子类 Child 新增属性 c 和 d ,则 Child 对象在堆中的内存布局大致如下:
[Parent对象部分] | [Child对象部分]
-----------------|-----------------
a: 数据 | c: 数据
b: 数据 | d: 数据
-----------------|-----------------
四、关键要点总结
- 类加载顺序:父类静态成员 → 子类静态成员。
- 对象创建顺序:父类非静态成员初始化 → 父类构造函数 → 子类非静态成员初始化 → 子类构造函数。
- 内存复用:子类对象包含父类对象的所有非静态成员,避免重复分配内存,体现继承的代码复用性。
- 多态的内存本质:引用变量指向子类对象,但编译时受父类类型限制,运行时根据实际对象类型调用方法(动态绑定)。
示例验证
通过以下代码可观察继承关系的内存分配顺序:
class Base {
static { System.out.println("Base静态块"); }
{ System.out.println("Base实例块"); }
Base() { System.out.println("Base构造函数"); }
}
class Sub extends Base {
static { System.out.println("Sub静态块"); }
{ System.out.println("Sub实例块"); }
Sub() { System.out.println("Sub构造函数"); }
}
public class InheritanceMemory {
public static void main(String[] args) {
new Sub(); // 输出顺序:Base静态块 → Sub静态块 → Base实例块 → Base构造 → Sub实例块 → Sub构造
}
}
理解继承的内存分配有助于掌握多态、构造函数调用等核心机制,若有具体疑问可以进一步探讨哦~