29、Java并发应用程序的测试与监控

Java并发应用程序的测试与监控

在Java并发编程中,了解并发对象的状态对于调试和优化应用程序至关重要。Java并发API提供了许多方法来监控各种并发对象,下面将详细介绍如何监控不同的并发对象。

1. 监控线程

线程是Java并发API中最基本的元素。 Thread 类提供了一些方法来获取线程的相关信息:
- getId() :返回线程的唯一标识符,是一个长正整数。
- getName() :返回线程的名称,默认格式为 Thread-xxx ,可以在构造函数或使用 setName() 方法修改。
- getPriority() :返回线程的优先级,默认优先级为5,可以使用 setPriority() 方法修改。
- getState() :返回线程的状态,返回值为 Enum Thread.State 类型,可能的值包括 NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED
- getStackTrace() :返回线程的调用栈,以 StackTraceElement 对象数组的形式返回。

以下是一个获取线程相关信息的示例代码:

System.out.println("**********************");
System.out.println("Id: " + thread.getId());
System.out.println("Name: " + thread.getName());
System.out.println("Priority: " + thread.getPriority());
System.out.println("Status: " + thread.getState());
System.out.println("Stack Trace");
for(StackTraceElement ste : thread.getStackTrace()) {
    System.out.println(ste);
}
System.out.println("**********************\n");
2. 监控锁

锁是Java并发API提供的基本同步元素之一,定义在 Lock 接口和 ReentrantLock 类中。 ReentrantLock 类提供了一些方法来获取锁的状态:
- getOwner() :返回当前持有锁的线程对象。
- hasQueuedThreads() :返回一个布尔值,指示是否有线程正在等待获取该锁。
- getQueueLength() :返回等待获取该锁的线程数量。
- getQueuedThreads() :返回一个 Collection<Thread> 对象,包含等待获取该锁的线程对象。
- isFair() :返回一个布尔值,指示锁的公平属性状态。
- isLocked() :返回一个布尔值,指示该锁是否被某个线程持有。
- getHoldCount() :返回当前线程获取该锁的次数。

由于 getOwner() getQueuedThreads() 方法是受保护的,无法直接访问。可以实现自己的 Lock 类来提供这些信息,例如:

public class MyLock extends ReentrantLock {
    private static final long serialVersionUID = 8025713657321635686L;
    public String getOwnerName() {
        if (this.getOwner() == null) {
            return "None";
        }
        return this.getOwner().getName();
    }
    public Collection<Thread> getThreads() {
        return this.getQueuedThreads();
    }
}

以下是一个获取锁相关信息的示例代码:

System.out.println("************************\n");
System.out.println("Owner : " + lock.getOwnerName());
System.out.println("Queued Threads: " + lock.hasQueuedThreads());
if (lock.hasQueuedThreads()) {
    System.out.println("Queue Length: " + lock.getQueueLength());
    System.out.println("Queued Threads: ");
    Collection<Thread> lockedThreads = lock.getThreads();
    for (Thread lockedThread : lockedThreads) {
        System.out.println(lockedThread.getName());
    }
}
System.out.println("Fairness: " + lock.isFair());
System.out.println("Locked: " + lock.isLocked());
System.out.println("Holds: "+lock.getHoldCount());
System.out.println("************************\n");
3. 监控执行器

执行器框架允许你执行并发任务,而无需担心线程的创建和管理。 ThreadPoolExecutor 类提供了一些方法来获取执行器的状态:
- getActiveCount() :返回正在执行任务的线程数量。
- getCompletedTaskCount() :返回已执行并完成的任务数量。
- getCorePoolSize() :返回线程池的核心线程数。
- getLargestPoolSize() :返回线程池中同时存在的最大线程数。
- getMaximumPoolSize() :返回线程池中可以同时存在的最大线程数。
- getPoolSize() :返回线程池当前的线程数。
- getTaskCount() :返回已提交给执行器的任务数量,包括等待、运行和已完成的任务。
- isTerminated() :如果调用了 shutdown() shutdownNow() 方法,并且执行器已完成所有待执行任务,则返回 true ,否则返回 false
- isTerminating() :如果调用了 shutdown() shutdownNow() 方法,但执行器仍在执行任务,则返回 true ,否则返回 false

以下是一个获取 ThreadPoolExecutor 相关信息的示例代码:

System.out.println("*******************************************");
System.out.println("Active Count: "+executor.getActiveCount());
System.out.println("Completed Task Count: "+executor.getCompletedTaskCount());
System.out.println("Core Pool Size: "+executor.getCorePoolSize());
System.out.println("Largest Pool Size: "+executor.getLargestPoolSize());
System.out.println("Maximum Pool Size: "+executor.getMaximumPoolSize());
System.out.println("Pool Size: "+executor.getPoolSize());
System.out.println("Task Count: "+executor.getTaskCount());
System.out.println("Terminated: "+executor.isTerminated());
System.out.println("Is Terminating: "+executor.isTerminating());
System.out.println("*******************************************");
4. 监控Fork/Join框架

Fork/Join框架为可以使用分治技术实现的算法提供了一种特殊的执行器。 ForkJoinPool 类提供了一些方法来获取其状态:
- getParallelism() :返回为线程池设置的期望并行级别。
- getPoolSize() :返回线程池中的线程数量。
- getActiveThreadCount() :返回当前正在执行任务的线程数量。
- getRunningThreadCount() :返回不等待子任务完成的线程数量。
- getQueuedSubmissionCount() :返回已提交到线程池但尚未开始执行的任务数量。
- getQueuedTaskCount() :返回线程池工作窃取队列中的任务数量。
- hasQueuedSubmissions() :如果有已提交到线程池但尚未开始执行的任务,则返回 true ,否则返回 false
- getStealCount() :返回Fork/Join池执行工作窃取算法的次数。
- isTerminated() :如果Fork/Join池已完成执行,则返回 true ,否则返回 false

以下是一个获取 ForkJoinPool 相关信息的示例代码:

System.out.println("**********************");
System.out.println("Parallelism: "+pool.getParallelism());
System.out.println("Pool Size: "+pool.getPoolSize());
System.out.println("Active Thread Count: "+pool.getActiveThreadCount());
System.out.println("Running Thread Count: "+pool.getRunningThreadCount());
System.out.println("Queued Submission: "+pool.getQueuedSubmissionCount());
System.out.println("Queued Tasks: "+pool.getQueuedTaskCount());
System.out.println("Queued Submissions: "+pool.hasQueuedSubmissions());
System.out.println("Steal Count: "+pool.getStealCount());
System.out.println("Terminated : "+pool.isTerminated());
System.out.println("**********************");
5. 监控Phaser

Phaser 是一种同步机制,允许你执行可以分为多个阶段的任务。 Phaser 类提供了一些方法来获取其状态:
- getArrivedParties() :返回已完成当前阶段的注册方数量。
- getUnarrivedParties() :返回尚未完成当前阶段的注册方数量。
- getPhase() :返回当前阶段的编号,第一个阶段的编号为0。
- getRegisteredParties() :返回 Phaser 中注册的方数量。
- isTerminated() :返回一个布尔值,指示 Phaser 是否已完成执行。

以下是一个获取 Phaser 相关信息的示例代码:

System.out.println("*******************************************");
System.out.println("Arrived Parties: "+phaser.getArrivedParties());
System.out.println("Unarrived Parties: "+phaser.getUnarrivedParties());
System.out.println("Phase: "+phaser.getPhase());
System.out.println("Registered Parties: "+phaser.getRegisteredParties());
System.out.println("Terminated: "+phaser.isTerminated());
System.out.println("*******************************************");
6. 监控流

Java 8引入的流机制允许你以并发方式处理大量数据。流类除了 isParallel() 方法(用于指示流是否为并行流)外,没有提供其他方法来获取流的状态,但提供了 peek() 方法,可以在方法管道中记录流操作或转换的日志信息。

以下是一个计算前999个数字平方的平均值的示例代码:

double result=IntStream.range(0,1000)
    .parallel()
    .peek(n -> System.out.println(Thread.currentThread().getName()+": Number "+n))
    .map(n -> n*n)
    .peek(n -> System.out.println(Thread.currentThread().getName()+": Transformer "+n))
    .average()
    .getAsDouble();
7. 监控并发应用程序

JDK提供了一些工具来编译、执行和生成Javadoc文档,其中 Java VisualVM 是一个图形化工具,可以显示JVM中正在执行的应用程序的信息。可以在JDK安装目录的 bin 目录中找到 jvisualvm.exe ,也可以为Eclipse安装插件( Eclipse launcher for VisualVM )来集成其功能。

启动 Java VisualVM 后,在屏幕左侧的 Applications 选项卡中可以看到当前用户在系统中运行的所有Java应用程序。双击某个应用程序,将显示五个选项卡:
- Overview :显示应用程序的一般信息,包括PID、主机名、主类、参数、JVM版本、Java版本、Java Home、JVM标志、JVM参数和系统属性。
- Monitor :以图形方式显示应用程序使用的CPU、内存、类和线程信息。可以通过右上角的复选框选择要查看的信息,还可以使用 Perform GC 按钮立即执行垃圾回收,使用 Heap Dump 按钮保存应用程序的当前状态。
- Threads :显示应用程序线程随时间的演变,包括活动线程数、守护线程数和线程状态的时间线。点击 Thread Dump 按钮可以查看每个线程的堆栈跟踪信息。
- Sampler :显示应用程序对内存和CPU的使用信息,与 Profiler 选项卡类似,但获取数据的方式不同。
- Profiler :显示应用程序对内存和CPU的使用信息,与 Sampler 选项卡类似,但获取数据的方式不同。

监控对象方法总结

监控对象 方法 描述
线程 getId() 返回线程的唯一标识符
线程 getName() 返回线程的名称
线程 getPriority() 返回线程的优先级
线程 getState() 返回线程的状态
线程 getStackTrace() 返回线程的调用栈
getOwner() 返回当前持有锁的线程对象
hasQueuedThreads() 指示是否有线程正在等待获取该锁
getQueueLength() 返回等待获取该锁的线程数量
getQueuedThreads() 返回等待获取该锁的线程对象集合
isFair() 指示锁的公平属性状态
isLocked() 指示该锁是否被某个线程持有
getHoldCount() 返回当前线程获取该锁的次数
执行器 getActiveCount() 返回正在执行任务的线程数量
执行器 getCompletedTaskCount() 返回已执行并完成的任务数量
执行器 getCorePoolSize() 返回线程池的核心线程数
执行器 getLargestPoolSize() 返回线程池中同时存在的最大线程数
执行器 getMaximumPoolSize() 返回线程池中可以同时存在的最大线程数
执行器 getPoolSize() 返回线程池当前的线程数
执行器 getTaskCount() 返回已提交给执行器的任务数量
执行器 isTerminated() 指示执行器是否已完成所有待执行任务
执行器 isTerminating() 指示执行器是否正在关闭但仍在执行任务
Fork/Join框架 getParallelism() 返回线程池的期望并行级别
Fork/Join框架 getPoolSize() 返回线程池中的线程数量
Fork/Join框架 getActiveThreadCount() 返回当前正在执行任务的线程数量
Fork/Join框架 getRunningThreadCount() 返回不等待子任务完成的线程数量
Fork/Join框架 getQueuedSubmissionCount() 返回已提交但尚未开始执行的任务数量
Fork/Join框架 getQueuedTaskCount() 返回线程池工作窃取队列中的任务数量
Fork/Join框架 hasQueuedSubmissions() 指示是否有已提交但尚未开始执行的任务
Fork/Join框架 getStealCount() 返回Fork/Join池执行工作窃取算法的次数
Fork/Join框架 isTerminated() 指示Fork/Join池是否已完成执行
Phaser getArrivedParties() 返回已完成当前阶段的注册方数量
Phaser getUnarrivedParties() 返回尚未完成当前阶段的注册方数量
Phaser getPhase() 返回当前阶段的编号
Phaser getRegisteredParties() 返回Phaser中注册的方数量
Phaser isTerminated() 指示Phaser是否已完成执行

监控流程

graph LR
    A[选择监控对象] --> B{对象类型}
    B -- 线程 --> C[使用线程监控方法]
    B -- 锁 --> D[使用锁监控方法]
    B -- 执行器 --> E[使用执行器监控方法]
    B -- Fork/Join框架 --> F[使用Fork/Join框架监控方法]
    B -- Phaser --> G[使用Phaser监控方法]
    B -- 流 --> H[使用流监控方法]
    B -- 应用程序 --> I[使用Java VisualVM监控]
    C --> J[获取线程信息]
    D --> K[获取锁信息]
    E --> L[获取执行器信息]
    F --> M[获取Fork/Join框架信息]
    G --> N[获取Phaser信息]
    H --> O[获取流信息]
    I --> P[选择应用程序]
    P --> Q{选择选项卡}
    Q -- Overview --> R[查看一般信息]
    Q -- Monitor --> S[查看图形信息]
    Q -- Threads --> T[查看线程演变信息]
    Q -- Sampler --> U[查看内存和CPU使用信息]
    Q -- Profiler --> V[查看内存和CPU使用信息]

通过以上方法和工具,可以有效地监控Java并发应用程序,及时发现和解决问题,提高应用程序的性能和稳定性。

各监控选项卡详细信息

1. Overview选项卡

Overview选项卡提供了应用程序的一般信息,这些信息对于了解应用程序的运行环境和配置非常有帮助。具体信息如下:
| 信息类型 | 描述 |
| — | — |
| PID | 应用程序的进程ID,用于唯一标识该应用程序在系统中的进程。 |
| 主机名 | 执行应用程序的机器名称。 |
| 主类 | 实现 main() 方法的类的完整名称,即应用程序的入口类。 |
| 参数 | 传递给应用程序的参数列表。 |
| JVM版本 | 执行应用程序的JVM版本。 |
| Java版本 | 运行应用程序的Java版本。 |
| Java Home | 系统中JDK的安装位置。 |
| JVM标志 | 与JVM一起使用的标志,用于配置JVM的行为。 |
| JVM参数 | 传递给JVM以执行应用程序的参数。 |
| 系统属性 | 系统的属性及其值,可以通过 System.getProperties() 方法获取。 |

2. Monitor选项卡

Monitor选项卡以图形化的方式展示了应用程序对CPU、内存、类和线程的使用情况,有助于直观地了解应用程序的资源消耗情况。
- CPU图形 :显示应用程序使用的CPU百分比,通过观察该图形可以了解应用程序的CPU负载情况。
- Heap图形 :展示了堆内存的总大小以及应用程序使用的堆内存大小。同时,也可以看到关于Metaspace(JVM用于存储类的内存区域)的相同信息。
- Classes图形 :显示应用程序使用的类的数量。
- Threads图形 :显示应用程序内部正在运行的线程数量。

此外,该选项卡还提供了两个按钮:
- Perform GC :立即在应用程序中执行一次垃圾回收,有助于释放不再使用的内存。
- Heap Dump :允许保存应用程序的当前状态,以便后续分析。保存后会出现一个新的选项卡,其中包含了堆转储的详细信息,可以通过不同的子选项卡查看应用程序在堆转储时刻的状态。

3. Threads选项卡

Threads选项卡展示了应用程序线程随时间的演变情况,提供了以下重要信息:
- 活动线程数 :应用程序中当前存在的线程数量。
- 守护线程数 :应用程序中被标记为守护线程的线程数量。
- 时间线 :展示了线程随时间的状态变化,使用颜色代码表示线程的不同状态,同时显示线程的运行时间和存在时间。在“Total”列的右侧有一个箭头,点击后可以选择要显示的列。

该选项卡还提供了 Thread Dump 按钮,点击后会出现一个新的选项卡,其中包含了应用程序中每个正在运行的线程的堆栈跟踪信息,有助于分析线程的执行情况和排查问题。

4. Sampler和Profiler选项卡

Sampler和Profiler选项卡都用于显示应用程序对内存和CPU的使用信息,但它们获取数据的方式不同。这两个选项卡可以帮助开发者了解应用程序的性能瓶颈,找出哪些部分消耗了大量的内存或CPU资源。

Java VisualVM使用步骤

使用Java VisualVM监控Java并发应用程序的步骤如下:
1. 启动Java VisualVM :在JDK安装目录的 bin 目录中找到 jvisualvm.exe 并启动。如果使用Eclipse,可以安装 Eclipse launcher for VisualVM 插件来集成其功能。
2. 选择应用程序 :在屏幕左侧的 Applications 选项卡中,找到并双击要监控的Java应用程序。
3. 查看信息 :根据需要选择不同的选项卡查看应用程序的相关信息,如Overview选项卡查看一般信息,Monitor选项卡查看图形化的资源使用信息等。
4. 执行操作 :在Monitor选项卡中,可以使用 Perform GC 按钮执行垃圾回收,使用 Heap Dump 按钮保存应用程序的当前状态;在Threads选项卡中,点击 Thread Dump 按钮查看线程的堆栈跟踪信息。

总结

在Java并发编程中,对并发对象和应用程序进行有效的监控是非常重要的。通过使用Java并发API提供的各种方法,可以监控线程、锁、执行器、Fork/Join框架、Phaser和流等并发对象的状态,及时发现潜在的问题。同时,Java VisualVM作为一个强大的图形化工具,提供了丰富的信息和功能,帮助开发者直观地了解应用程序的运行情况,进行性能分析和问题排查。

监控方法对比

监控方式 优点 缺点 适用场景
Java并发API方法 代码层面监控,可精确获取对象状态;可集成到应用程序中实时监控 需要编写代码,不够直观;只能获取特定对象的信息 开发过程中对特定并发对象进行详细监控
Java VisualVM 图形化界面,直观展示应用程序整体信息;提供多种监控维度 无法深入到代码细节;可能会对应用程序性能产生一定影响 运行时对应用程序进行全面监控和性能分析

监控决策流程

graph LR
    A[发现问题或需要监控] --> B{问题类型}
    B -- 特定对象状态 --> C[使用Java并发API方法]
    B -- 应用程序整体性能 --> D[使用Java VisualVM]
    C --> E[编写代码获取对象信息]
    D --> F[启动Java VisualVM]
    F --> G[选择应用程序]
    G --> H{选择监控维度}
    H -- Overview --> I[查看一般信息]
    H -- Monitor --> J[查看图形信息]
    H -- Threads --> K[查看线程演变信息]
    H -- Sampler --> L[查看内存和CPU使用信息]
    H -- Profiler --> M[查看内存和CPU使用信息]

通过合理选择监控方式和工具,开发者可以更好地掌握Java并发应用程序的运行状态,提高应用程序的性能和稳定性。在实际开发和运维过程中,应根据具体需求和场景灵活运用这些监控方法和工具。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值