面试题答案
一键面试变量作用域规则
- 成员变量:
- 实例成员变量:属于类的实例对象。其作用域是整个类,从对象创建时开始存在,直到对象被垃圾回收。在类的任何方法、构造函数、初始化块中都可以访问。例如:
public class Example {
private int instanceVar; // 实例成员变量
public Example() {
instanceVar = 10;
}
public void method() {
System.out.println(instanceVar);
}
}
- **静态成员变量**:属于类本身,而不是类的实例。作用域也是整个类,在类加载时创建,直到类被卸载才销毁。可以通过类名直接访问,也可以通过对象访问。例如:
public class Example {
private static int staticVar; // 静态成员变量
public static void method() {
staticVar = 20;
System.out.println(staticVar);
}
}
- 局部变量:在方法、构造函数、代码块内部声明。作用域从声明处开始,到包含该变量声明的块结束。例如:
public class Example {
public void method() {
int localVar; // 局部变量
localVar = 30;
{
int innerVar = 40; // 局部变量,作用域仅限于此块
System.out.println(innerVar);
}
// 这里不能访问 innerVar
System.out.println(localVar);
}
}
JVM内存管理与变量生命周期
- 实例成员变量:存储在堆内存中,随着对象的创建而分配内存,当对象不再被任何引用指向,且经过垃圾回收器标记清除等算法确认该对象不可达后,对象及其包含的实例成员变量所占用的内存会被回收。
- 静态成员变量:存储在方法区(在Java 8及之后的版本中,静态变量存储在元空间),在类加载时分配内存,类卸载时才释放内存。由于类一般不会轻易卸载,所以静态变量的生命周期通常和应用程序的生命周期一样长。
- 局部变量:基本数据类型的局部变量存储在栈内存中,随着方法的调用而分配内存,方法结束时,栈帧弹出,局部变量所占用的内存被释放。引用类型的局部变量在栈中存储的是对象的引用,对象本身存储在堆中,当局部变量的作用域结束,其对堆中对象的引用失效,如果堆中对象没有其他引用指向它,垃圾回收器会在合适的时候回收该对象。
可能出现的内存泄漏场景及避免方法
- 静态集合类引起的内存泄漏:
- 场景:如果静态集合类(如
static List
、static Map
)中存放了大量对象,且这些对象在不再需要时没有从集合中移除,由于静态变量的生命周期长,这些对象也无法被垃圾回收,从而导致内存泄漏。例如:
- 场景:如果静态集合类(如
public class MemoryLeakExample {
private static List<Object> list = new ArrayList<>();
public void addObject(Object obj) {
list.add(obj);
}
}
- **避免方法**:及时从静态集合中移除不再需要的对象。例如,在对象不再使用时,调用`list.remove(obj)`。
2. 监听器和回调未注销引起的内存泄漏: - 场景:注册监听器或回调函数后,如果没有相应的注销机制,当注册监听器的对象不再使用,但监听器仍被其他对象引用,导致该对象无法被垃圾回收,造成内存泄漏。比如在Swing编程中,注册了窗口监听器但未注销。 - 避免方法:提供注销监听器或回调的方法,在对象不再使用时调用该方法,移除监听器或回调。 3. 对象内部类持有外部类引用导致的内存泄漏: - 场景:如果一个非静态内部类对象被长期持有,由于内部类默认持有外部类的引用,会导致外部类对象无法被垃圾回收,即使外部类对象不再被需要。例如:
public class OuterClass {
private InnerClass inner;
public OuterClass() {
inner = new InnerClass();
}
private class InnerClass {
// InnerClass 持有 OuterClass 的引用
}
}
- **避免方法**:如果内部类不需要长期持有外部类引用,可以将内部类声明为静态内部类。如果需要访问外部类非静态成员,可以通过弱引用的方式持有外部类对象。