当前位置: 首页 > news >正文

java --- 性能优化01

目录


前言

一、过度依赖自动内存管理而不进行内存调优

概述:

1.1  错误场景:

1.2  错误表现及危害:

1.3  解决方法:

1.4  案例:

1.5  示例

1.5.1  优化代码,减少不必要的内存分配

二、不合理的数据库查询

概述:

2.1 错误场景:

2.2 错误表现及危害:

2.3 解决方法:

2.4 案例:

三、过度使用同步机制

3.1 错误场景:

3.2 错误表现及危害:

使用方式

使用方式

3.3 解决方法:

3.4 案例:


前言

在我们深入探讨 Java 性能优化中常见的错误之前,先来简单回顾一下热门技术如何帮助提升性能。
(1)内存管理优化就像整理你的书桌。通过合理设置堆内存的大小、减少垃圾回收的次数、选择合适的垃圾回收器,可以让程序运行得更顺畅。
(2)代码优化技巧类似于给汽车升级发动机和减轻车身重量。优化算法和数据结构、避免过度同步、优化字符串操作、提高代码可读性,都能让程序跑得更快、更高效。
(3)数据库访问优化好比在餐厅高峰期增加服务员数量和简化点餐流程。使用连接池、优化 SQL 查询、采用异步数据库访问,可以大大提升数据库操作的速度和效率。
(4)多线程与并发优化就像在工厂里合理安排工人。通过合理设置线程数量、使用线程池、避免死锁和竞争条件,充分发挥多核处理器的优势,提升整体性能。
(5)性能监控与调优工具如 JProfiler、VisualVM 和 Arthas,就像医生的诊断设备。它们帮助我们深入了解程序的运行状况,及时发现问题并进行优化。


一、过度依赖自动内存管理而不进行内存调优

概述:

        Java 的自动垃圾回收机制(GC)确实方便,但过度依赖而不做内存调优可能导致性能问题,尤其是在处理大数据或高并发时。

1.1  错误场景:

        开发者常常忽视内存调优,依赖默认设置。然而,不同应用的内存需求不同,特别是复杂项目,默认的 GC 设置很难满足要求。比如,处理大量请求的系统如果堆内存过小,GC 会频繁触发,导致程序卡顿。

1.2  错误表现及危害:

  • 堆内存过小:会导致频繁的垃圾回收(GC),影响程序运行速度,类似于过度打扫导致工作中断。
  • 堆内存过大:会浪费系统资源,影响其他进程运行,类似于小房间里装了过大的空调,占用过多空间。

1.3  解决方法:

  • 调整 JVM 参数:合理设置堆内存大小 (-Xms 和 -Xmx),确保符合应用的内存需求。
  • 选择合适的垃圾回收器:根据应用场景,选择适合的 GC,比如 G1 GC 更适合大内存应用。
  • 使用内存分析工具:通过工具如 JProfiler、VisualVM 检测内存泄漏,并优化代码。

1.4  案例:

        某电商系统因堆内存设置过小和选择错误的 GC,导致系统频繁卡顿。通过调整内存设置、切换到 G1 GC 并修复内存泄漏,系统性能提高了 30%,用户体验显著改善。

1.5  示例

1.5.1  优化代码,减少不必要的内存分配

(1)减少对象创建

        通过避免频繁创建短生命周期的对象,可以减少 GC 压力,提高内存效率。

示例:

// 不推荐:每次循环都创建新字符串对象
for (int i = 0; i < 1000; i++) {String result = new String("Result: " + i);
}// 推荐:使用StringBuilder拼接,减少对象创建
StringBuilder result = new StringBuilder();
for (int i = 0; i < 1000; i++) {result.append("Result: ").append(i);
}

作用:StringBuilder 拼接字符串比 String 高效,因为它避免了创建多个中间对象,减少了堆内存的使用。

(2)对象池化

        对于频繁使用的大对象,创建对象池复用实例,避免频繁的创建和销毁。

示例:

// 不推荐:每次都创建新对象,增加GC负担
for (int i = 0; i < 1000; i++) {Connection conn = new Connection();  // 假设这是一个重量级对象// 使用连接conn.close();
}// 推荐:使用对象池复用对象
ObjectPool<Connection> pool = new ObjectPool<>(Connection::new);  // 假设存在ObjectPool类
for (int i = 0; i < 1000; i++) {Connection conn = pool.borrow();// 使用连接pool.return(conn);
}

作用:通过对象池技术,可以避免频繁的对象创建和销毁,从而减轻 GC 负担,提升系统性能。

(3)内存泄漏检测与避免

        避免内存泄漏的常见做法是确保不必要的对象不会被长期引用。

// 不推荐:使用静态集合会导致对象长期被引用,内存无法回收
private static List<Object> cache = new ArrayList<>();public void addToCache(Object obj) {cache.add(obj);
}// 推荐:使用局部变量,及时释放对象的引用
public void addToCache(Object obj) {List<Object> tempCache = new ArrayList<>();tempCache.add(obj);// 操作完成后,tempCache将被垃圾回收
}

作用:静态变量会导致对象长期被引用,无法被 GC 回收,可能导致内存泄漏。避免使用静态集合或对象池时,及时清理无用对象。

(4)设置合理的线程池

        合理使用线程池管理并发任务,避免创建过多线程耗尽内存。

// 不推荐:为每个任务创建新线程,浪费资源
for (int i = 0; i < 1000; i++) {new Thread(() -> {// 执行任务}).start();
}// 推荐:使用线程池管理线程
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {executor.submit(() -> {// 执行任务});
}
executor.shutdown();

作用:线程池复用线程,避免了频繁的线程创建和销毁,降低了内存和 CPU 的使用,提高了系统性能。

(5)优化集合的使用

        根据需求,选择合适的集合大小和类型,减少内存浪费。

// 不推荐:使用默认初始大小(例如ArrayList初始容量为10),超出后会频繁扩容
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {list.add("Item " + i);
}// 推荐:指定合理的初始容量,避免扩容
List<String> list = new ArrayList<>(1000);
for (int i = 0; i < 1000; i++) {list.add("Item " + i);
}

作用:指定集合的初始容量避免了集合扩容时的频繁复制,减少了内存的浪费和 CPU 负担。


二、不合理的数据库查询

概述

        数据库查询的优化对 Java 应用程序的性能影响重大。如果查询不合理,极有可能成为性能瓶颈,尤其在数据量大、查询频繁的场景下。

2.1 错误场景:

在很多应用中,数据库查询是非常常见且重要的操作,然而一些常见的错误却容易被忽视:

  • 没有为经常查询的字段创建索引。
  • 进行不必要的全表扫描。
  • 编写复杂且低效的 SQL 查询语句。

2.2 错误表现及危害:

  1. 未创建索引

    • 这是最常见的错误之一。当查询的字段没有索引时,数据库必须遍历整个表来找到匹配的结果。这就像在图书馆里没有目录的情况下寻找一本书,效率极低。
    • 危害:随着数据量增加,查询时间急剧上升,导致响应缓慢,影响用户体验。
  2. 全表扫描

    • 如果查询没有条件限制或者没有使用索引,数据库会执行全表扫描,查找符合条件的记录。这就好比在沙漠中寻找一颗特定的沙粒,浪费大量时间和资源。
    • 危害:全表扫描会占用大量 I/O 资源和 CPU 资源,尤其在大表中,性能下降明显。
  3. 复杂低效的 SQL 查询

    • 编写复杂的 SQL 查询,如嵌套子查询、过多的表连接,容易导致数据库的查询优化器难以生成高效的执行计划。这类似于让一辆跑车在泥泞的路上行驶,无法发挥应有的速度。
    • 危害:复杂查询会增加数据库的处理时间,导致查询响应缓慢,甚至可能锁住表,影响其他查询操作。

2.3 解决方法:

  1. 创建索引

    • 为经常查询的字段(如主键、外键、常用过滤字段)创建索引,就像为图书馆编制目录。索引可以加速查询,减少遍历数据的时间。
    • 示例:在 MySQL 中,可以为 user 表的 email 字段创建索引:
      CREATE INDEX idx_email ON user(email);
  2. 避免全表扫描

    • 尽量通过索引、条件查询或限制返回的记录来避免全表扫描。确保 WHERE 子句中使用了索引字段,避免进行无条件查询。
    • 示例:使用带有条件过滤的查询:
      SELECT * FROM user WHERE email = 'example@example.com';
  3. 优化 SQL 查询

    • 避免使用过于复杂的子查询,尝试将其改写为连接查询。同时,减少不必要的表连接,尽量保持查询简单高效。
    • 示例:将嵌套子查询改写为 JOIN 查询:

    -- 不推荐:嵌套子查询

SELECT * FROM orders WHERE user_id IN (SELECT id FROM user WHERE status = 'active');

    -- 推荐:使用 JOIN

SELECT orders.* FROM orders JOIN user ON orders.user_id = user.id WHERE user.status = 'active';

2.4 案例:

        一个电商平台在促销高峰期遇到了数据库查询缓慢的问题,导致大量用户体验不佳。经过分析,发现热门商品的查询没有创建索引,且某些查询使用了全表扫描。开发团队通过以下措施优化了查询性能:

  • 为关键字段(如商品ID、类别)创建索引
  • 优化了 SQL 查询,减少了不必要的表连接和嵌套子查询
  • 通过条件查询避免全表扫描

优化后,数据库查询响应速度提高了近 50%,用户的下单转化率也随之增加。


三、过度使用同步机制

概述
        在多线程编程中,适当的同步机制可以确保数据一致性,但过度使用同步会降低程序的并发性能,导致资源浪费和性能下降。

3.1 错误场景:

        在开发多线程程序时,开发者通常会使用同步机制(如 synchronized 或 ReentrantLock)来防止数据竞争。但不加选择地使用这些机制会导致线程阻塞,甚至引发性能瓶颈。


同步(Synchronous):简单来说,程序必须等待任务完成才能执行下一步。

异步(Asynchronous):简单来说,程序无需等待,可以同时处理多个任务。


3.2 错误表现及危害:

  1. 过度使用 synchronized 或 ReentrantLock:
    • 使用同步机制时,如果过多的代码块被锁住,会造成大量线程同时争抢锁资源,导致性能大幅下降。这类似于让很多人同时通过一扇非常狭窄的门,大家互相等待,谁也无法顺利通过。
    • 危害:线程阻塞增加,系统的并发处理能力被限制,响应时间变长,尤其是在高并发的场景下,性能会急剧下降。

(1)synchronized 是 Java 内置的同步机制,它可以用来锁住某个方法或代码块,以保证同一时刻只有一个线程可以执行该方法或代码块。

使用方式
  • 同步方法:在方法前加 synchronized,表示整个方法在同一时间只能由一个线程访问。

public synchronized void updateValue() {// 只有一个线程可以同时进入这个方法value++;
}
  • 同步代码块:将 synchronized 加在特定的代码块上,可以只对需要保护的部分加锁,其他代码部分不受影响。
public void updateValue() {// 非线程安全代码synchronized (this) {// 线程安全部分value++;}
}

(2)ReentrantLock 是 Java java.util.concurrent.locks 包中的类,功能上类似于 synchronized,但比 synchronized 提供了更灵活的控制方式。

使用方式
  • 锁的基本操作:在代码中显式加锁和释放锁。
import java.util.concurrent.locks.ReentrantLock;public class Example {private final ReentrantLock lock = new ReentrantLock(); // 创建锁对象public void updateValue() {lock.lock();  // 获取锁try {value++;  // 线程安全操作} finally {lock.unlock();  // 释放锁}}
}

(3)ReentrantLock 的特点:

  • 手动加锁和释放锁:开发者需要手动调用 lock.lock() 来加锁,并在操作完成后使用 lock.unlock() 释放锁。相比于 synchronized,它提供了更明确的锁定机制。
  • 可以尝试获取锁:ReentrantLock 提供了 tryLock() 方法,允许线程尝试获取锁而不会一直等待(synchronized 一旦等待锁,就无法中途退出)。
if (lock.tryLock()) {try {// 拿到锁后执行} finally {lock.unlock();}
} else {// 没有拿到锁
}

公平锁:你可以创建 ReentrantLock 时指定为公平锁,即让等待时间最长的线程优先获取锁,而不是让任意线程随机获取锁。

ReentrantLock lock = new ReentrantLock(true); // 公平锁

3.3 解决方法:

1.  缩小同步代码块的范围

  • 只对真正需要保证线程安全的部分加锁,而不是整个方法或大块代码。将同步范围缩小到最小可行的粒度,减少锁的持有时间。
  • 示例
// 不推荐:同步整个方法
public synchronized void updateValue() {// 一些无关的操作value++;
}// 推荐:同步关键代码块
public void updateValue() {// 一些无关的操作synchronized(this) {value++;}
}
  • 作用:缩小同步范围可以减少锁的争用,提高并发效率。

2.  使用无锁数据结构或原子类

  • 对于简单的计数或状态更新操作,考虑使用 java.util.concurrent 包中的无锁数据结构或原子操作类(如 AtomicInteger),避免传统的同步机制。
  • 示例
// 使用AtomicInteger替代synchronized
AtomicInteger counter = new AtomicInteger(0);public void increment() {counter.incrementAndGet();  // 无需使用锁
}

作用:原子操作类通过无锁机制实现线程安全,可以在高并发场景下显著提高性能。


3.  使用多线程调试工具

  • 借助 Java 的多线程调试工具(如 VisualVM、JConsole 等),可以实时监控线程的状态和竞争情况,发现并解决潜在的线程竞争和锁争用问题。
  • 作用:及时发现过度使用锁导致的性能问题,并通过工具调整锁的策略和使用范围,优化系统性能。

3.4 案例:

某金融交易系统在高并发的情况下,频繁出现交易处理延迟。经过深入分析,发现系统中多个关键方法都使用了 synchronized 进行同步,导致大量线程被阻塞,无法并发执行。开发团队通过以下方法进行了优化:

  1. 缩小了同步代码块的范围,减少不必要的锁争用。
  2. 使用了 AtomicInteger替代同步机制,处理高频计数操作。
  3. 通过 VisualVM 工具监控线程状态,进一步优化了锁的使用。

最终,系统的并发处理能力提升了 30%,交易处理响应时间显著缩短,能够在高峰时段平稳运行。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Linux:体系结构和操作系统管理
  • Flutter的升级和降级步骤
  • QMT软件怎么申请开通?QMT软件到底是谁在用啊?QMT量化软件K线驱动介绍
  • tensor连接和拆分
  • 搜维尔科技:ART光学空间定位虚拟交互工业级光学跟踪系统
  • sourcetree配置ssh连接gitee
  • 中国企业500强!最新名单揭晓→
  • JavaScript高级进阶(二)
  • IGNAV_NHC分析
  • 【深度学习】训练过程中一个OOM的问题,太难查了
  • 多人开发小程序设置体验版的痛点
  • 视频推拉流/直播点播EasyDSS平台安装失败并报错“install mediaserver error”是什么原因?
  • Centos7.9部署Gitlab-ce-16.9
  • 【人工智能学习笔记】3_2 机器学习基础之机器学习经典算法介绍
  • 程序员如何写笔记并整理资料?
  • JavaScript-如何实现克隆(clone)函数
  • 收藏网友的 源程序下载网
  • [译]Python中的类属性与实例属性的区别
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • 【Under-the-hood-ReactJS-Part0】React源码解读
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • Apache Zeppelin在Apache Trafodion上的可视化
  • Docker 笔记(2):Dockerfile
  • express.js的介绍及使用
  • Git初体验
  • HTTP那些事
  • IndexedDB
  • JAVA并发编程--1.基础概念
  • Java面向对象及其三大特征
  • Mithril.js 入门介绍
  • mysql常用命令汇总
  • react 代码优化(一) ——事件处理
  • socket.io+express实现聊天室的思考(三)
  • v-if和v-for连用出现的问题
  • Vue组件定义
  • Webpack入门之遇到的那些坑,系列示例Demo
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 通过npm或yarn自动生成vue组件
  • 项目实战-Api的解决方案
  • 源码之下无秘密 ── 做最好的 Netty 源码分析教程
  • 运行时添加log4j2的appender
  • elasticsearch-head插件安装
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • ​【已解决】npm install​卡主不动的情况
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • #每天一道面试题# 什么是MySQL的回表查询
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (5)STL算法之复制
  • (7) cmake 编译C++程序(二)
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (LLM) 很笨
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (附源码)php投票系统 毕业设计 121500