2021-08-19 11:42:58
OPPO Java岗社招面经问题解答如下:
讲一下Java的虚拟机
核心组成:Java虚拟机(JVM)由类加载子系统、运行时数据区(方法区、堆、虚拟机栈、本地方法栈、程序计数器)、执行引擎和本地方法接口组成。
关键功能:负责加载.class文件、管理内存、执行字节码、提供垃圾回收机制,并实现跨平台运行。
内存模型:堆(对象实例存储)、方法区(类元数据)、栈(线程私有,存储局部变量表等)。
如何让虚拟机中的方法区直接爆满?
动态生成大量类:通过反射或字节码操作(如ASM、CGLIB)在运行时生成大量类,填满方法区的永久代(JDK 8前)或元空间(JDK 8+)。
加载超大JAR包:引入包含海量类的第三方库,或通过自定义类加载器重复加载类。
极端场景:使用工具生成重复但不同的类定义(如通过循环生成类名不同的类)。
讲一下Java的垃圾回收机制
分代收集:堆分为新生代(Eden、Survivor区)和老年代,采用不同回收策略(如新生代用复制算法,老年代用标记-清除或标记-整理)。
触发条件:新生代Eden区满时触发Minor GC;老年代空间不足或调用System.gc()时触发Full GC。
常见算法:
标记-清除:标记无用对象后直接清除,产生内存碎片。
复制算法:将存活对象复制到另一块内存,适合新生代。
标记-整理:移动存活对象至一端,避免碎片化,适合老年代。
收集器类型:Serial、Parallel、CMS、G1(分区收集,兼顾吞吐量和低延迟)。
把Java中的容器类都讲一下
Collection接口:
List:有序可重复,如ArrayList(动态数组)、LinkedList(双向链表)、Vector(线程安全)。
Set:无序不可重复,如HashSet(哈希表)、TreeSet(红黑树排序)、LinkedHashSet(保持插入顺序)。
Queue:队列,如LinkedList(双端队列)、PriorityQueue(优先级队列)。
Map接口:键值对存储,如HashMap(哈希表)、TreeMap(红黑树排序)、LinkedHashMap(保持插入顺序)、ConcurrentHashMap(线程安全)。
工具类:Collections(排序、同步化)、Arrays(数组操作)。
Java中的锁是怎么实现的?
synchronized:
对象锁:修饰方法或代码块,基于对象头中的Mark Word实现,通过monitorenter/monitorexit指令控制。
类锁:修饰静态方法,锁为Class对象。
Lock接口:如ReentrantLock(可重入锁)、ReadWriteLock(读写锁),通过CAS(Compare-And-Swap)和AQS(AbstractQueuedSynchronizer)框架实现。
其他锁:Semaphore(信号量)、CountDownLatch(倒计时锁)、CyclicBarrier(循环屏障)。
引用计数法有什么缺点?
计数器开销大:每次对象引用变更需更新计数器,高频操作影响性能。
内存占用高:计数器需足够位数(如32位系统需32位计数器),可能占用对象内存的1/3。
无法处理循环引用:如对象A引用B,B引用A,计数器始终≥1,导致内存泄漏。
说一下TCP的三次握手和四次挥手
三次握手:
客户端发送SYN包(seq=x)到服务器,进入SYN_SENT状态。
服务器回复SYN+ACK包(seq=y, ack=x+1),进入SYN_RCVD状态。
客户端发送ACK包(ack=y+1),连接建立,双方进入ESTABLISHED状态。
四次挥手:
客户端发送FIN包(seq=u),进入FIN_WAIT_1状态。
服务器回复ACK包(ack=u+1),进入CLOSE_WAIT状态。
服务器发送FIN包(seq=v),进入LAST_ACK状态。
客户端回复ACK包(ack=v+1),进入TIME_WAIT状态,等待2MSL后关闭。
为什么挥手时有个TIME_WAIT?即2MSL
确保最后一个ACK到达:防止服务器未收到ACK而重传FIN包,客户端需保留足够时间(2MSL,最大段生命周期的两倍)等待重传。
避免新旧连接混淆:确保网络中残留的旧连接数据包失效,避免影响新连接。
浏览器输入URL到页面出来的流程
缓存检查:依次查询浏览器缓存、系统缓存、路由器缓存。
DNS解析:将域名转换为IP地址(可能涉及递归查询或迭代查询)。
TCP连接:通过三次握手建立连接。
HTTP请求:发送请求数据包(如GET/POST)。
服务器处理:返回响应数据(如HTML、CSS、JS)。
页面渲染:解析HTML生成DOM树,加载CSS构建渲染树,执行JS交互,最终渲染页面。
后续交互:可能通过AJAX动态加载数据。
操作系统中的死锁怎么形成的,怎么预防死锁?
形成条件:
互斥:资源独占使用。
占有并等待:持有资源同时请求其他资源。
非抢占:资源不能被强制释放。
循环等待:存在资源等待环路。
预防方法:
破坏占有并等待:一次性申请所有资源。
破坏非抢占:允许抢占资源。
破坏循环等待:按顺序申请资源。
避免死锁:通过银行家算法等动态检测资源分配状态。
进程和线程有什么区别?
资源分配:进程是资源分配的基本单位,线程是CPU调度的基本单位。
开销:进程切换开销大(需切换内存空间),线程切换开销小(共享同一进程资源)。
通信:进程间通信需IPC(如管道、消息队列),线程间可直接共享内存。
安全性:进程间独立性强,线程间共享数据需同步机制(如锁)。
线程的几种状态
新建(NEW):线程创建但未启动。
可运行(RUNNABLE):包括就绪(等待CPU调度)和运行中状态。
阻塞(BLOCKED):等待获取锁或I/O操作。
等待(WAITING):调用Object.wait()或Thread.join()进入无限期等待。
超时等待(TIMED_WAITING):调用Thread.sleep()或带超时的wait()。
终止(TERMINATED):线程执行完毕或异常退出。
线程池用过没,怎么使用,流程是什么?
核心组件:
核心线程数(corePoolSize):常驻线程数量。
最大线程数(maximumPoolSize):线程池允许的最大线程数。
工作队列(BlockingQueue):存放待执行任务的队列。
线程工厂(ThreadFactory):创建线程的工厂。
拒绝策略(RejectedExecutionHandler):队列满时的处理策略(如抛异常、丢弃任务)。
使用流程:
创建线程池(如Executors.newFixedThreadPool(int nThreads))。
提交任务(execute()或submit())。
线程池管理任务执行(核心线程不足时创建新线程,队列满时触发拒绝策略)。
关闭线程池(shutdown()或shutdownNow())。
创建线程有哪些方法,有什么区别,你一般怎么创建?
继承Thread类:重写run()方法,通过start()启动。
实现Runnable接口:实现run()方法,通过Thread(Runnable target)构造线程。
实现Callable接口:实现call()方法(可返回结果),通过FutureTask和ExecutorService执行。
线程池:通过Executors工具类创建线程池,复用线程资源。
推荐方式:优先使用线程池(避免频繁创建销毁线程的开销),其次使用Runnable或Callable接口(避免单继承限制)。