在Java中,对象的内存管理机制是理解面向对象编程的基础。所有通过new关键字创建的对象实例都会被分配在堆内存中,而变量本身只是存储着指向堆内存中对象的引用地址。这种设计带来了几个重要特性:
关于对象数组的内存布局,需要特别注意:
java复制Person[] people = new Person[3];
people[0] = new Person("张三");
这段代码的内存结构是:
关键理解:数组本身是引用类型,存储的是对象引用而非对象本身。这种间接访问的设计使得数组元素可以动态指向不同对象,也方便实现null值表示空缺。
从JDK8开始,方法区由元空间(Metaspace)实现,直接使用本地内存而非JVM堆内存。这解决了永久代内存溢出的问题。元空间存储:
配置参数示例:
code复制-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=256M
现代JVM采用分代收集算法,将堆划分为:
典型配置比例:
code复制-XX:NewRatio=2 # 老年代/新生代=2:1
-XX:SurvivorRatio=8 # Eden/Survivor=8:1
每个线程私有,存储栈帧(Stack Frame)。每个方法调用对应一个栈帧,包含:
栈深度过大时抛出StackOverflowError,可通过-Xss调整栈大小。
构造方法是对象初始化的核心机制,具有以下特点:
在继承体系中,构造方法调用形成链式关系:
java复制class Animal {
Animal(String name) {
System.out.println("Animal构造:" + name);
}
}
class Dog extends Animal {
Dog() {
super("默认名称"); // 必须显式调用
System.out.println("Dog构造");
}
}
执行new Dog()的输出顺序:
java复制public class User {
private final String username;
public User(String username) {
this.username = Objects.requireNonNull(username, "用户名不能为null");
}
}
访问控制的实际应用场景:
| 修饰符 | 同类 | 同包 | 子类 | 其他包 |
|---|---|---|---|---|
| private | ✓ | ✗ | ✗ | ✗ |
| default | ✓ | ✓ | ✗ | ✗ |
| protected | ✓ | ✓ | ✓ | ✗ |
| public | ✓ | ✓ | ✓ | ✓ |
典型用法:
重写(Override)必须遵守以下约束:
示例:
java复制class Shape {
public Shape create() throws IOException {
return new Shape();
}
}
class Circle extends Shape {
@Override
public Circle create() throws FileNotFoundException {
return new Circle();
}
}
java复制class Parent {
String name = "Parent";
void show() {
System.out.println("Parent show");
}
}
class Child extends Parent {
String name = "Child";
@Override
void show() {
super.show(); // 调用父类方法
System.out.println(super.name); // 访问父类字段
}
}
java复制public class Student {
private String name;
private int age;
// 无参构造
public Student() {}
// getter/setter
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("姓名不能为空");
}
this.name = name;
}
// 可添加业务方法
public boolean isAdult() {
return age >= 18;
}
}
java复制public record Student(String name, int age) {
// 自动生成final字段、构造方法、equals等
}
java复制@Data
@Builder
public class Student {
@NonNull private String name;
@Min(0) private int age;
}
除了语法检查外,现代IDE利用@Override实现:
java复制public class AnnotationDemo {
@SuppressWarnings("unchecked")
public List<String> getNames() {
return (List<String>) Collections.emptyList();
}
@Deprecated(since = "1.8", forRemoval = true)
public void oldMethod() {}
}
对象存活判定依据:
典型内存泄漏场景:
java复制// 静态集合引起的内存泄漏
private static List<Object> cache = new ArrayList<>();
void addToCache(Object obj) {
cache.add(obj); // 对象永远无法被回收
}
java复制public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
// 只有getter没有setter
public int getX() { return x; }
public int getY() { return y; }
// 返回新对象而非修改现有对象
public ImmutablePoint move(int dx, int dy) {
return new ImmutablePoint(x + dx, y + dy);
}
}
java复制public class Computer {
private final String cpu;
private final String ram;
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
}
public static class Builder {
private String cpu;
private String ram;
public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
// 使用方式
Computer pc = new Computer.Builder()
.cpu("i7")
.ram("16G")
.build();
bash复制jmap -dump:format=b,file=heap.hprof <pid>
bash复制jstack <pid> > thread.txt
在面向对象实践中,我发现最常出现的问题往往源于对基础概念的理解偏差。比如将引用变量等同于对象本身,或者混淆值传递与引用传递。建议通过内存模型图辅助理解,并多用调试工具观察实际内存状态。对于复杂对象关系,可以尝试使用UML类图进行可视化设计。