在Java开发中,队列(Queue)和栈(Stack)是两种最基础的数据结构,它们的核心区别体现在数据操作规则上。栈遵循LIFO(Last In First Out)原则,就像餐厅里叠放的餐盘——最后放上去的盘子总是最先被取用。而队列则遵循FIFO(First In First Out)原则,如同排队买票——先来的人先获得服务。
Java中的Stack类继承自Vector,提供了push、pop、peek等核心方法。实际开发中更推荐使用Deque接口的ArrayDeque实现栈功能,因为Stack作为遗留类存在同步开销且设计上不够纯粹。
java复制// 现代Java推荐的栈实现方式
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); // 入栈
int top = stack.pop(); // 出栈
栈的经典应用包括:
Java集合框架中的队列主要分为两类:
java复制Queue<String> queue = new LinkedList<>();
queue.offer("任务1"); // 入队
String task = queue.poll(); // 出队
队列的典型使用场景:
关键区别:栈的push/pop都在同一端操作,而队列的offer/poll分别在首尾两端操作。这种结构差异直接决定了它们各自适用的场景。
Java的引用系统远比表面看起来复杂,从JDK1.2开始引入的四种引用类型,为内存管理提供了不同粒度的控制手段。理解这些引用类型的差异,是写出高性能Java应用的基础。
这是我们日常使用最多的引用类型,通过new创建的对象默认都是强引用。只要强引用存在,垃圾收集器就绝不会回收该对象。
java复制Object obj = new Object(); // 强引用
强引用的特点:
内存泄漏的典型场景就是无意中保持了不必要的强引用,比如静态集合缓存数据却忘记清理。
软引用描述还有用但非必需的对象。在内存不足时,这些对象会被回收(在OOM之前)。适合实现内存敏感的缓存。
java复制SoftReference<byte[]> cache = new SoftReference<>(new byte[10_000_000]);
byte[] data = cache.get(); // 可能返回null
使用要点:
弱引用关联的对象只能生存到下一次GC前。无论内存是否充足,都会被回收。常用于实现规范化映射(如WeakHashMap)。
java复制WeakReference<Object> weakRef = new WeakReference<>(new Object());
System.gc();
assert weakRef.get() == null; // GC后立即失效
典型应用场景:
最特殊的引用类型,无法通过get()获取对象实例。唯一作用是在对象被回收时收到系统通知,用于执行最后的资源清理。
java复制ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
// 当对象被回收时,phantomRef会被加入queue
使用场景:
经验法则:从强到弱的引用类型,体现了从"绝不能回收"到"随时可回收"的不同强度。合理搭配使用可以构建更智能的内存管理策略。
JVM的引用系统分为符号引用(Symbolic Reference)和直接引用(Direct Reference)两个层次,这是类加载和动态链接的核心机制。
符号引用是编译阶段生成的元数据,用文本形式描述被引用的目标。包括:
class复制// 字节码中的符号引用示例
invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
特点:
直接引用是经过解析后的最终形式,可以是:
转换过程:
JVM规范允许两种解析策略:
常见解析错误:
java复制// 典型解析错误案例
public class Main {
public static void main(String[] args) {
System.out.println(NonExistClass.message); // 触发NoClassDefFoundError
}
}
结合软引用和弱引用构建智能缓存系统:
java复制public class SmartCache<K,V> {
private final Map<K,V> strongCache = new ConcurrentHashMap<>();
private final Map<K,SoftReference<V>> softCache = new ConcurrentHashMap<>();
private final Map<K,WeakReference<V>> weakCache = new ConcurrentHashMap<>();
public void put(K key, V value) {
strongCache.put(key, value);
softCache.put(key, new SoftReference<>(value));
weakCache.put(key, new WeakReference<>(value));
}
}
利用ReferenceQueue实现泄漏检测:
java复制ReferenceQueue<Object> leakQueue = new ReferenceQueue<>();
Set<WeakReference<Object>> refSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
new Thread(() -> {
while (true) {
Reference<?> ref = leakQueue.remove();
if (refSet.remove(ref)) {
System.out.println("检测到资源泄漏: " + ref);
}
}
}).start();
针对引用处理的关键参数:
-XX:SoftRefLRUPolicyMSPerMB=N:控制软引用存活时间(默认1000ms/MB)-XX:+PrintReferenceGC:打印引用处理日志-XX:+ParallelRefProcEnabled:启用并行引用处理性能陷阱:过度使用弱引用会导致GC负担加重,因为每次GC都需要处理引用队列。在高频交易系统中应谨慎使用。
bash复制jmap -dump:live,format=b,file=heap.hprof <pid>
误用强引用导致缓存无法释放
java复制// 错误示例
static Map<User, Profile> cache = new HashMap<>();
忽略ReferenceQueue导致资源未清理
java复制// 正确做法
PhantomReference<Connection> ref = new PhantomReference<>(conn, queue);
// 需要单独线程处理queue
混淆引用类型使用场景
开启GC日志查看引用处理:
bash复制-Xlog:gc+ref*=debug
典型日志解读:
code复制[GC ref-proc] SoftReference: 1000->500
[GC ref-proc] WeakReference: 200->50
[GC ref-proc] FinalReference: 30->10
[GC ref-proc] PhantomReference: 5->0
数字变化反映各引用类型在GC前后的数量变化,异常波动可能预示问题。