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并发应用程序的运行状态,提高应用程序的性能和稳定性。在实际开发和运维过程中,应根据具体需求和场景灵活运用这些监控方法和工具。
超级会员免费看

被折叠的 条评论
为什么被折叠?



