(一)异常分类
Java 异常体系庞大且严谨,分为受检异常(Checked Exception)和非受检异常(Unchecked Exception)。受检异常要求在代码中必须显式处理,如IOException,它通常代表着可能在程序运行中出现,但并非程序逻辑错误导致的外部问题,像文件读取时文件不存在的情况。非受检异常包含运行时异常(RuntimeException)及其子类,如NullPointerException、
ArrayIndexOutOfBoundsException等,这类异常往往是由于程序逻辑缺陷引起,编译器不会强制要求处理,但一旦出现会导致程序中断。
(Java异常调优一站式解决方案系统提升解决异常问题和调优能力完结)---“夏のke”---weiranit---.---fun/1857/
(二)异常处理原则
- 明确捕获:捕获异常时,应精确捕获特定类型的异常,避免使用宽泛的Exception捕获所有异常。这有助于定位问题根源,例如在处理数据库操作异常时,分别捕获SQLException的不同子类,如SQLSyntaxErrorException表示 SQL 语法错误,SQLIntegrityConstraintViolationException表示违反数据库完整性约束,这样能针对性地解决问题。
- 合理抛出:若当前方法无法处理异常,应合理抛出,将异常传递给能更好处理它的上层方法。在一个业务逻辑复杂的服务类中,底层的数据访问方法可能抛出DataAccessException,而服务层可以捕获该异常,进行日志记录后,抛出更具业务含义的BusinessServiceException给控制层,以便统一处理。
- 避免隐藏异常:在捕获异常进行处理时,不能简单地忽略异常,必须采取适当措施,如记录日志、恢复默认状态等。例如,在文件写入操作中捕获到IOException,不能只是打印一条简单错误信息,而应详细记录异常堆栈信息,以便排查问题,同时尝试关闭文件流,防止资源泄漏。
(三)自定义异常
当 Java 内置异常无法满足业务需求时,需要创建自定义异常。自定义异常通常继承自Exception(受检异常)或RuntimeException(非受检异常)。例如,在电商系统中,针对商品库存不足的情况,可以创建
StockInsufficientException,继承自BusinessException(自定义的业务异常基类),通过添加额外的字段,如商品 ID、缺货数量等,能更准确地传递异常信息,方便上层业务处理。
二、性能调优总结
(一)JVM 调优
- 内存模型优化:理解 JVM 内存模型,包括堆(Heap)、栈(Stack)、方法区(Method Area)等。合理调整堆内存大小,通过-Xms(初始堆大小)和-Xmx(最大堆大小)参数设置。在一个高并发处理大量数据的应用中,若频繁出现OutOfMemoryError,可能需要增大堆内存;同时,根据应用特点,合理分配新生代(Young Generation)和老年代(Old Generation)的比例,通过-XX:NewRatio参数调整,以优化垃圾回收性能。
- 垃圾回收调优:了解不同的垃圾回收器,如 Serial、Parallel、CMS、G1 等。根据应用场景选择合适的垃圾回收器,例如对于响应时间敏感的应用,如 Web 前端服务,CMS 或 G1 垃圾回收器可能更合适,因为它们能减少垃圾回收停顿时间;而对于吞吐量要求高的后台批处理任务,Parallel 垃圾回收器可能是更好的选择。通过调整垃圾回收器相关参数,如-XX:MaxGCPauseMillis(最大垃圾回收停顿时间)、-XX:GCTimeRatio(垃圾回收时间占总时间的比例)等,进一步优化垃圾回收性能。
(二)数据库调优
- SQL 优化:编写高效的 SQL 语句,避免全表扫描。使用索引是关键,通过EXPLAIN命令分析 SQL 执行计划,查看是否使用了索引以及索引使用是否合理。在一个查询用户信息的 SQL 中,若查询条件为WHERE user_name = 'xxx',而user_name字段没有创建索引,数据库可能会进行全表扫描,导致查询效率低下,此时创建索引能显著提升查询速度。同时,避免使用子查询和复杂的关联查询,尽量将其改写为更高效的连接查询。
- 索引优化:创建合适的索引,但避免过度创建。索引并非越多越好,过多的索引会增加数据插入、更新和删除的时间开销。在一个订单表中,若频繁根据订单号和客户 ID 查询订单信息,可以创建复合索引(order_id, customer_id),注意索引字段顺序,将选择性高的字段放在前面,以提高索引利用率。定期维护索引,如重建索引、删除不再使用的索引,以保证索引的高效性。
- 连接池配置:合理配置数据库连接池参数,如最大连接数、最小连接数、连接超时时间等。在一个高并发的 Web 应用中,若连接池最大连接数设置过小,可能会导致大量请求因无法获取连接而等待,甚至超时;若设置过大,可能会耗尽数据库资源。通过监控连接池的使用情况,如活跃连接数、等待连接数等指标,动态调整连接池参数,以优化数据库连接性能。
(三)代码调优
- 算法优化:选择合适的算法和数据结构,例如在需要频繁插入和删除元素的场景中,使用链表(LinkedList)比数组(ArrayList)更合适,因为链表的插入和删除操作时间复杂度为 O (1),而数组为 O (n)。在查找操作频繁的场景中,使用哈希表(HashMap)或树(TreeMap)能提高查找效率,哈希表的查找时间复杂度平均为 O (1),树的查找时间复杂度为 O (log n)。对现有算法进行优化,如在排序算法中,对于小规模数据,选择插入排序可能比快速排序更高效,因为插入排序在小规模数据下的常数时间开销更小。
- 并发编程优化:在多线程编程中,避免死锁和资源竞争。使用锁机制时,尽量使用粒度更细的锁,如在一个多线程访问共享数据的场景中,若使用粗粒度的锁,会导致大量线程等待,降低并发性能,此时可以将共享数据按功能或模块进行划分,使用多个细粒度的锁。合理使用并发工具类,如ConcurrentHashMap、CopyOnWriteArrayList等,这些类在多线程环境下具有更好的性能和线程安全性。例如,ConcurrentHashMap采用分段锁机制,允许多个线程同时访问不同的段,提高了并发访问效率。
三、实战案例复盘
(一)内存泄漏问题排查与解决
在一个长时间运行的 Java 应用中,发现内存使用量持续增长,最终导致OutOfMemoryError。通过jmap命令生成堆转储文件(.hprof 文件),使用VisualVM或MAT(Memory Analyzer Tool)工具分析该文件。发现某个对象在创建后一直未被释放,经过代码排查,发现是由于一个静态集合类中错误地保存了大量对象引用,导致这些对象无法被垃圾回收。通过修正代码,在对象不再使用时及时从集合中移除引用,解决了内存泄漏问题。
(二)数据库死锁问题分析与处理
在一个多线程访问数据库的应用中,出现了数据库死锁现象。首先查看数据库的死锁日志,确定死锁涉及的事务和 SQL 语句。通过分析发现,两个事务在不同的顺序下获取锁,导致死锁。例如,事务 A 先获取了表 A 的锁,然后尝试获取表 B 的锁;而事务 B 先获取了表 B 的锁,然后尝试获取表 A 的锁。为了解决这个问题,调整事务获取锁的顺序,使所有事务都按照相同的顺序获取锁,或者采用超时机制,当事务获取锁超时后,自动回滚事务,避免死锁的发生。
(三)高并发场景下性能瓶颈分析与优化
在一个高并发的电商秒杀系统中,用户请求量突然增加时,系统响应时间急剧变长。通过使用Arthas等线上诊断工具,发现瓶颈在于数据库的写入操作。由于大量并发写入,数据库的 I/O 负载过高。为了解决这个问题,采用了缓存机制,将部分热点数据先写入缓存(如 Redis),然后异步批量写入数据库。同时,对数据库进行了分库分表,将数据分散存储,降低单个数据库的负载,从而提升了系统在高并发场景下的性能。