Java面试深度解析:内存溢出、锁机制、线程进程与更多

需积分: 0 6 下载量 91 浏览量 更新于2024-08-04 收藏 14KB MD 举报
在Java编程中,内存溢出(Out of Memory)和内存泄漏是两个常见的性能问题,它们会导致程序运行缓慢或甚至崩溃。 内存溢出通常发生在以下情况: 1. **大量对象创建**:当程序创建大量对象而没有及时释放时,可能会耗尽堆内存,导致内存溢出。 2. **大对象分配**:如果一个对象占用的内存过大,可能导致单次分配就超出堆内存限制。 3. **静态变量引用**:静态变量生命周期与类加载器相同,如果静态变量引用大量对象,即使对象不再使用,也无法被垃圾回收。 4. **内存泄露**:如上述代码示例所示,当对象不再使用但仍然有引用指向它时,GC无法回收这些对象,导致内存不断消耗直至溢出。 内存泄漏的理解与检测: 内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,导致系统内存的持续消耗。内存泄漏可能由以下原因引起: 1. **丢失引用**:如上述示例,当对象被添加到集合后,即使不再使用,集合的引用仍阻止垃圾回收。 2. **循环引用**:两个或更多对象互相引用,形成一个循环,导致它们都无法被垃圾回收。 3. **单例模式不当**:如果单例类持有大量资源,且不正确地管理,可能导致内存泄漏。 4. **线程局部变量**:线程结束时,线程局部变量存储的数据如果不释放,也可能造成内存泄漏。 乐观锁和悲观锁是并发控制策略: 乐观锁假设并发冲突较少,读多写少,所以在读取数据时不加锁,只有在更新数据时才检查是否被其他事务修改,如版本号机制、CAS(Compare and Swap)操作。 悲观锁则认为并发冲突频繁,读写都加锁,确保数据安全,如数据库的行级锁定。 线程与进程的区别: 1. **资源分配单位**:进程是操作系统资源分配的基本单位,拥有独立的内存空间;线程是CPU调度的基本单位,共享进程的内存空间。 2. **开销**:创建和销毁进程的开销较大,而线程创建和销毁成本低。 3. **通信**:进程间通信复杂,线程间通信直接,因为它们共享内存。 `Session`是Hibernate持久化层的一个概念,它的几种方法: - `save()`:持久化对象,返回对象的ID,但不立即保存到数据库,需要调用flush()。 - `update()`:更新已存在的持久化对象。 - `merge()`:将 detached 对象的状态合并到持久化上下文中,然后更新。 - `lock()`:锁定一个对象,防止其他事务修改。 - `saveOrUpdate()`:根据对象是否存在自动调用 save() 或 update()。 - `persist()`:JPA 中的方法,首次关联对象,使其成为持久化对象,但不立即保存。 代理的三种方式: 1. **静态代理**:通过接口实现,手动创建代理类。 2. **动态代理**:Java提供`java.lang.reflect.Proxy`类,运行时动态创建代理对象。 3. `CGLIB`代理:使用字节码技术动态创建代理类,当目标类没有接口时使用。 `stackoverflow`错误通常是由于递归调用过深或无限循环导致栈溢出。 `permgen space`错误是老版本JVM中永久代(存放Class信息)内存不足,现代JVM已改为Metaspace。 分代收集算法是垃圾回收的一种策略,将堆分为新生代和老年代,新生代采用复制算法,老年代使用标记-清除或标记-整理算法。 同步方法(`synchronized`修饰方法)和同步块(`synchronized`代码块)都是Java的并发控制手段,用于保证线程安全。选择哪个更好取决于具体场景,同步方法对整个方法进行加锁,粒度较大;同步块可以更细粒度控制,只对必要的部分加锁,提高并发效率。 Java中的编译期常量(`final static`字段)在编译阶段就确定值,存在于常量池中,使用时直接引用,提高了效率。但过度使用可能导致内存浪费,因为它们始终驻留在内存中,且不可变,可能阻碍优化。 死锁是多个线程相互等待对方释放资源导致的僵局。避免死锁的方法包括: 1. 避免嵌套锁。 2. 按照相同的顺序获取锁。 3. 设置超时并回滚。 4. 使用死锁检测算法。 5. 避免持有锁时请求新锁。 了解并掌握这些知识点对于Java开发者来说至关重要,它们能帮助你在面试中脱颖而出,并在实际开发中解决性能问题,提高程序稳定性。