37、Java Web应用性能优化与资源管理

Java Web应用性能优化与资源管理

1. 对象池与框架中的池化

对象池是提高应用性能的重要手段。GenericKeyedObjectPool 的设置较为复杂,但它提供的功能强大。Commons 项目为实现健壮的对象池编写了大量代码,用户只需实现一个简单的接口,就能让对象池管理特定类型的对象。

如果需求不复杂,自己实现对象池,特别是使用 SoftReferences(GenericKeyedObjectPool 不支持)就足够了。但如果需要健壮的对象池,Commons 池是个不错的选择。

在不同的框架中,对象池的使用情况有所不同:
| 框架 | 池化情况 |
| ---- | ---- |
| Model 2 框架 | 上一节的池化代码无需修改即可使用,设置代码可移至框架的原生设置代码中。例如,使用 Struts 时,可继承其 Action servlet 并在其中执行对象池的设置代码,然后将控制权传递给父 servlet。控制器中的代码移至 Struts 的 Action 类和其他框架的相应控制器代理中。 |
| Tapestry | 该框架实现了自己的池化代码,通常不需要额外的池化操作。可以通过其 API 将自己的对象添加到 Tapestry 的池中。 |
| InternetBeans Express | 不支持上述代码,因为它不使用边界类。 |

2. 可扩展性设计

Web 应用的独特之处在于难以判断峰值并发用户数量,特别是对于公共网站。因此,在设计时要确保网站能够优雅地扩展。

Servlet API 已经处理了 Web 应用的大部分可扩展性问题,它允许一个 servlet 实例处理多个并发请求,除非在 servlet 中实现了 SingleThreadModel 接口。这个接口会严重削弱 Web 应用的可扩展性,应尽量避免使用。

2.1 何时升级到 EJB

Enterprise JavaBeans(EJBs)是 Java 中构建可扩展 Web 应用的现成解决方案。EJBs 通过提前创建对象池、数据库连接池、线程池等实现可扩展性。

然而,使用 EJBs 也有成本,包括金钱和时间。构建 Web 应用时可以使用多种免费的 servlet 引擎,但使用 EJBs 必须有完整的应用服务器。开源应用服务器很少,商业应用服务器则非常昂贵。

此外,编写 EJBs 很复杂,尽管规范成熟后编写变得容易一些,但仍然是一项艰巨的任务。包含分布式组件会增加整个应用的工作量,即使应用划分良好,设置和调试时间也不容忽视。

2.2 为未来塑造架构

不必急于决定迁移到 EJB。如果遵循良好的设计准则,将用户界面、数据库逻辑和业务逻辑分离,就可以轻松地将项目迁移到 EJB,只需替换应用的一层即可。

关键是要将边界类和实体清晰分离。在非 EJB 应用中,边界类处理所有持久化细节;迁移到 EJB 时,边界层成为应用服务器中运行的 EJBs 的代理层。

3. 何时进行优化

优化有一些重要的规则:
- 规则 1:不要过早优化。
- 规则 2(仅适用于专家):暂时不要优化。

过早优化往往是徒劳的,很多时候开发者认为应用的某个部分是瓶颈,花费大量时间去修复,结果性能提升甚微,而应用的其他部分却运行缓慢。

从一开始就确保架构正确很重要。每个项目都应该编写概念验证项目(在敏捷编程方法中称为“架构探索”),确保整体设计和架构没有重大问题。项目进行过程中,可以对可用的子系统进行性能测试,确定真正的热点后再花时间修复,然后再次测试。

应该依靠工具来识别应用中的问题,即使只是使用 SDK 附带的分析器,也能获得关于应用性能的客观事实,而不是猜测。

4. 性能测量与监控

要确定应用的性能,必须客观地进行测量。在 Web 应用中测量内存很困难,因为 Java 虚拟机与操作系统的交互方式以及 servlet 引擎的存在。可以通过 VM 的内置分析器(通过命令行选项调用)来分析代码中的“热点”,分析分析器生成的大型分析文档以确定应用的性能瓶颈。商业分析器也能更轻松地测量内存。

另一种性能监控方式是负载测试,模拟大量用户。开源的 JMeter 项目可以设置测试并以多种格式报告结果,商业负载测试工具通常很昂贵。

5. 性能优化策略

Web 开发中有各种性能陷阱,以下是一些优化策略:
- 对象创建 :创建无状态类或构建对象重用基础设施可以降低对象创建的成本。
- 对象引用 :注意多余的对象引用,特别是集合类中的引用。
- 字符串操作 :动态字符串操作应始终使用 StringBuffers 而不是 Strings。
- 对象池 :创建大量预构造对象并在需要时检索,而不是按需创建新对象。可以使用 SoftReferences 或 WeakReferences 创建对象池,WeakHashMap 类使用 WeakReferences 便于创建松散缓存。如果有更复杂的池化需求,Jakarta 项目的 Commons 池提供了复杂的对象池化设施。

6. 资源管理概述

即使在性能调优上投入大量精力,如果应用的资源管理不善,也无法取得良好的效果。因此,需要对可控制的资源进行有效管理,如内存。

Web 应用中有开发者可控制和不可控制的资源。不可控制的资源包括线程、文件句柄等应用服务器和底层操作系统的低级特性,解决这些资源问题的方法通常是升级或替换应用服务器或硬件。

接下来将重点介绍可控制的资源管理策略,包括缓存策略、设计模式的应用、优化 Java Naming and Directory Interface (JNDI) 使用以及处理标准集合中的资源问题等。

7. 缓存策略 - Flyweight 设计模式

内存是大型繁忙应用的瓶颈,也是 Web 开发中最容易出现问题和获得最大收益的领域。有效的缓存策略可以降低内存占用并提高应用速度。

Flyweight 设计模式是一种缓存方案,它使用共享来支持大量细粒度的对象引用。该模式基于规范对象的概念,一个规范对象代表该类型的所有其他对象。例如,在应用中,不必为每个用户创建产品列表,而是创建一个规范产品列表,每个用户持有对该列表的引用。

以 eMotherEarth 应用为例,默认设计为每个用户持有一个产品列表,这会浪费大量内存。因为产品对所有用户都是相同的,且产品特性很少改变。更好的设计是创建一个规范产品列表,每个用户持有对该列表的引用。

以下是实现 Flyweight 模式的步骤:
1. 构建规范产品列表并放入全局可访问位置 :修改欢迎控制器,在应用上下文中构建产品列表。

public void init() throws ServletException {
    String driverClass =
            getServletContext().getInitParameter("driverClass");
    String password =
            getServletContext().getInitParameter("password");
    String dbUrl =
            getServletContext().getInitParameter("dbUrl");
    String user =
            getServletContext().getInitParameter("user");
    DBPool dbPool =
            createConnectionPool(driverClass, password, dbUrl,
                                 user);
    getServletContext().setAttribute("dbPool", dbPool);
    buildFlyweightReferences(dbPool);
}

private void buildFlyweightReferences(DBPool dbPool) {
    ProductDb productDb = (ProductDb) getServletContext().
                          getAttribute("products");
    if (productDb == null) {
        productDb = new ProductDb();
        productDb.setDbPool(dbPool);
        List productList = productDb.getProductList();
        Collections.sort(productList, new IdComparator());
        getServletContext().setAttribute("products",
                productList);
    }
}
  1. 修改目录控制器从全局缓存中获取产品
public void doPost(HttpServletRequest request,
                   HttpServletResponse response) throws
        ServletException, IOException {
    HttpSession session = request.getSession(true);
    ensureThatUserIsInSession(request, session);
    List productReferences =
            (List) getServletContext().getAttribute("products");
    int start = getStartingPage(request);
    int recsPerPage = Integer.parseInt(getServletConfig().
            getInitParameter("recsPerPage"));
    int totalPagesToShow = calculateNumberOfPagesToShow(
            productReferences.size(), recsPerPage);
    String[] pageList =
            buildListOfPagesToShow(recsPerPage,
                                   totalPagesToShow);
    List outputList = getProductListSlice(productReferences,
            start, recsPerPage);
    sortPagesForDisplay(request, outputList);
    bundleInformationForView(request, start, pageList,
                             outputList);
    forwardToView(request, response);
}
  1. 修改排序方法 :为了避免新用户登录时获得与上一个排序用户相同的排序列表,将 IdComparator 移出 if 语句,在没有其他排序条件时应用该排序。
private void sortPagesForDisplay(HttpServletRequest request,
                                 List outputList) {
    String sortField = request.getParameter("sort");
    Comparator c = new IdComparator();
    if (sortField != null) {
        if (sortField.equalsIgnoreCase("price"))
            c = new PriceComparator();
        else if (sortField.equalsIgnoreCase("name"))
            c = new NameComparator();
    }
    Collections.sort(outputList, c);
}

这种缓存技术的副作用是每个用户都会对产品列表进行分页排序,但在记录数量较少的情况下,对性能影响不大。而且该控制器的一个特点是用户在排序前选择记录页面,这使得该设计模式的改造较为容易。如果排序在用户指定记录子集之前进行,控制器可能需要修改,但用户通常不会提出这样的请求。

Java Web应用性能优化与资源管理

8. 缓存策略 - 原理与注意事项

Flyweight 设计模式的缓存策略原理在于通过共享对象来减少内存占用。从流程图可以更清晰地看到其工作流程:

graph TD;
    A[应用启动] --> B[构建规范产品列表];
    B --> C[将列表放入应用上下文];
    D[用户请求产品信息] --> E[从应用上下文获取产品引用];
    E --> F[根据用户请求获取子集];
    F --> G[应用用户排序规则];
    G --> H[显示产品信息];

在使用这种缓存策略时,需要注意以下几点:
- 排序问题 :由于所有用户共享同一个列表,排序操作会影响到所有用户的子集。如前面所述,为了避免新用户获取到已排序的列表,需要对排序逻辑进行调整。
- 数据一致性 :规范产品列表是全局共享的,如果产品数据发生变化,需要及时更新该列表,以保证所有用户获取到的是最新数据。
- 并发访问 :多个用户可能同时访问和修改列表,需要考虑并发控制,避免数据不一致或出现异常。

9. 其他资源管理策略

除了缓存策略,还有其他一些资源管理策略可以提高应用的性能和资源利用率。

9.1 优化 Java Naming and Directory Interface (JNDI) 用法

JNDI 是 Java 中用于查找和访问命名资源的 API。在 Web 应用中,JNDI 常用于查找数据库连接池、消息队列等资源。为了优化 JNDI 的使用,可以采取以下措施:
- 缓存 JNDI 查找结果 :避免每次需要资源时都进行 JNDI 查找,可以将查找结果缓存起来,下次需要时直接从缓存中获取。
- 减少查找次数 :合理设计应用架构,尽量减少不必要的 JNDI 查找操作。

9.2 处理标准集合中的资源问题

在 Java 中,标准集合(如 List、Map 等)是常用的数据结构。但在使用这些集合时,需要注意资源问题:
- 避免内存泄漏 :确保集合中的对象在不再使用时能够被垃圾回收,避免因对象引用导致的内存泄漏。
- 选择合适的集合类型 :根据实际需求选择合适的集合类型,如需要快速查找可以使用 HashMap,需要有序存储可以使用 TreeMap。

10. 懒加载的使用

懒加载是一种延迟对象实例化的技术,即在需要对象时才进行实例化。懒加载可以提高资源利用率,但可能会影响性能。

10.1 何时使用懒加载
  • 对象创建成本高 :如果对象的创建过程比较复杂,消耗大量资源,可以考虑使用懒加载,避免不必要的对象创建。
  • 对象使用频率低 :对于不经常使用的对象,使用懒加载可以减少内存占用。
10.2 懒加载的实现

以下是一个简单的懒加载示例:

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

在这个示例中, LazySingleton 类的实例在第一次调用 getInstance() 方法时才会被创建。

11. 设计模式在资源管理中的应用

除了 Flyweight 设计模式,还有其他设计模式可以用于资源管理。

11.1 Façade 设计模式

Façade 设计模式为复杂的子系统提供一个简单的接口,使得客户端可以更方便地使用子系统。在资源管理中,Façade 模式可以用于封装资源的获取和释放操作,简化客户端代码。

以下是一个简单的 Façade 模式示例:

// 子系统类
class SubSystem1 {
    public void operation1() {
        System.out.println("SubSystem1 operation1");
    }
}

class SubSystem2 {
    public void operation2() {
        System.out.println("SubSystem2 operation2");
    }
}

// Façade 类
class Facade {
    private SubSystem1 subSystem1;
    private SubSystem2 subSystem2;

    public Facade() {
        subSystem1 = new SubSystem1();
        subSystem2 = new SubSystem2();
    }

    public void performOperations() {
        subSystem1.operation1();
        subSystem2.operation2();
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.performOperations();
    }
}

在这个示例中, Facade 类封装了 SubSystem1 SubSystem2 的操作,客户端只需要调用 Facade 类的 performOperations() 方法即可,无需了解子系统的具体实现。

11.2 总结设计模式的作用

设计模式在资源管理中具有重要作用,它们可以:
| 设计模式 | 作用 |
| ---- | ---- |
| Flyweight | 减少对象数量,降低内存占用 |
| Façade | 简化复杂系统的使用,提高代码的可维护性 |

12. 总结

在 Java Web 应用开发中,性能优化和资源管理是至关重要的。通过合理使用对象池、缓存策略、设计模式等技术,可以提高应用的性能和资源利用率,避免常见的性能陷阱。

在进行性能优化时,要遵循“不要过早优化”的原则,依靠工具客观地测量和分析应用的性能,找出真正的瓶颈并进行针对性的优化。同时,要注重资源管理,合理控制可控制的资源,如内存、JNDI 资源等,确保应用的稳定性和可扩展性。

未来,随着 Web 应用的不断发展和用户需求的增加,性能优化和资源管理将面临更多的挑战和机遇。开发者需要不断学习和掌握新的技术和方法,以应对这些挑战,为用户提供更高效、稳定的 Web 应用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值