推荐文章:开发实践|关于100以内的加减乘除法问题之我在客户现场遇到的bug
文章链接:https://cloud.tencent.com/developer/user/7221733
推荐原因:因为一次精度的缺失导致的错误bug,而衍生出的关于bug解决方案的思考:先排查数据,在数据准确无误的情况下,再去检查下工具,从属性配置上看看是否可以解决,当然有些不要求精度准确的情况下,也可以同客户商议解决办法,采取折中的方式来减少开发工作量。
如果真的要回忆关于印象深刻的bug,可能就要追溯到我刚实习的时候。而之所以那么深刻,可能是源自初入社会那份求知的信,亦或者是那时候遇到的人,对我如今选择的这条路影响深远。
2017年,实习,大数据组。
对于二三线城市来说,很多公司的计算机实习岗位,约等于无门槛免费劳动力。当然,我也不例外,在大数据实习的前两个月,除了整理工作文档,就是自己悄摸摸的学习。后来在机缘巧合之下,以运维的角色接触到了一个数据接入的项目。
程序主要分为三个部分:TCP数据接入、数据解析、写kafka。当时的问题就是程序启动之后,运行一段时间就内存溢出,进而造成的结果就是TCP阻塞以及数据量核对不上。当时怀疑是解析这部分的代码性能效率不高,因为解析这一块比较复杂,每条数据平局100个字段,每个字段都要根据规范中的字节长度和数据类型,将二进制转换成明文数据。
所以当时看他们就是铆足了劲,如何去优化解码逻辑。后来就来了一位大佬,就用jvisualvm定位出了问题所在:因为当时平台都是集中化建设,Hadoop还是1.x版本,所以kakfa也是0.8版本,正因为kafka版本太低,写入效率太慢,虽然整个程序占用cpu过高,但实际上二进制数一直阻塞在程序的queue中,最后导致内存溢出。
后来找到问题之后,在Hadoop2.x建设阶段,将kafka升级到了0.10版本,就解决了这个问题。当时自己空有jvm的理论知识,后来在这次经历中也算是将理论映进了现实,所以到至今都印象深刻,现在也特别推崇通过jvm来分析程序性能的方式。所以,本篇文章就主要大概复现上述问题,看看如何使用jvisualvm来分析程序性能问题。
为了复现上面的问题,原来程序的TCP数据接入、数据解析、写kafka三个部分,我分别进行了简单的代码替换。首先我们定义数据接入的模块,这里主要模拟数据写入queue的一个过程。
private static final ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
public static void dataReceive() {
String data = "data";
for (; ; ) {
// 生产数据并放入队列
queue.add(data);
try {
// 控制一下频率,防止过快内存溢出
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
这里主要是讲data写入queue,并为了防止内存溢出使用sleep来写入控制频率。然后我们实现数据解析以及写入kafka模块。
public static void dataAnalysis() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
String data = queue.poll();
for (int i1 = 0; i1 < 40000; i1++) {
// 模拟数据解析
System.out.print("");
}
try {
// 模拟控制写入kafka效率
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(data);
}
}, "thread" + i).start();
}
}
这里我启动了10个线程,并给每个线程编号。在run方法中,通过输出4万次空字符串来模拟数据解析过程,通过sleep来模拟写入kafka的效率问题。
启动程序之后,就看到了一个名为Test的java程序进程。点击进入,选择线程选项卡。
如图,我们可以看到thread0-10线程是由紫色、红色以及绿色组成。大部分都处于紫色,紫色代表线程休眠,少量的绿色部分表示是出于运行状态,也就是上述代码中的数据解析,红色是jvisualvm采样导致的,这个不用管。从最后的运行占比中也能看出,线程处理活动状态(解析)的比例都在10%之内,也就说明如果写入kafka效率过慢,会导致前面解析逻辑无法运行,从而最后导致性能下降。
当然,这里是不严谨的,为什么这么说呢。是因为我在代码中本来就显式使用了Thread.sleep,所以最后在jvisualvm中显示紫色也是很正常的。其实在生产中,写入kafka的线程是独立的,但是和数据解析的线程(thread0-10)是“串行的”。
数据解析之后会立即写入到kafka,本次循环需要再kafka写入成功之后返回响应,才会结束并重新执行上面的数据解析逻辑,但是写入kafka太慢了,需要一直等待。所以当时实际看到的现象是:thread0-10是如图中这种休眠状态,而写入kafka的线程一直是处于绿色运行状态。
进而推断出程序的效率低下,并不是因为计算资源不足或者数据解析逻辑导致的(如果一直处于绿色活动状态,那就是解析逻辑有问题或者计算资源不足)。
jvisualvm 是一个强大的性能分析工具,它是 JDK 自带的一部分,通常与 JDK 一起安装。jvisualvm 主要用于监控 Java 应用程序的性能,帮助开发人员了解应用程序的内存、CPU 使用情况,分析线程的执行情况,发现潜在的性能瓶颈,以及进行垃圾回收分析等。
换个理解方式,jvisualvm是jstat、jmap、jstack、jps等命令的可视化方案。在配置好jdk之后,在命令行中运行 jvisualvm 命令即可启动。
在jvisualvm中,你可以看到不同的选项卡,如 Monitor(监控)、Profiler(性能分析)、Heap Dump(堆转储)等。每个选项卡提供了不同的性能数据,帮助开发者分析 Java 应用的运行状态。
Monitor 选项卡提供了 JVM 的基本性能数据,包括:
Profiler 选项卡可以帮助开发者进行更深入的性能分析,主要通过以下几个方面进行:
垃圾回收是 Java 中的一个重要环节,jvisualvm 提供了垃圾回收的监控功能。通过 Garbage Collector 选项卡,可以查看垃圾回收的详细信息,包括:
堆转储是对 JVM 堆内存的快照,jvisualvm 可以生成堆转储文件并进行分析。堆转储可以帮助开发者了解 Java 堆中的对象分布、引用关系等,有助于诊断内存泄漏、过度的对象创建等问题。
同时也可以通过抽样器选项卡的内存选项,可以看到上面的这些指标:
通过 jvisualvm 提供的性能分析工具,开发者可以定位到性能瓶颈,从而进行优化。以下是一些常见的优化建议:
Profiler
分析方法调用的执行时间,发现消耗 CPU 资源较高的方法,并进行优化。Profiler
监控对象的创建情况,避免在频繁调用的方法中创建不必要的对象。jvisualvm 是一个非常强大的 Java 性能分析和调优工具,它为开发者提供了详细的内存使用情况、垃圾回收情况、CPU 使用情况等多维度的信息,帮助开发者发现和解决 Java 应用中的性能问题。
通过合理配置和使用 jvisualvm,开发者可以在开发过程中更好地监控和优化 Java 应用的性能,提升应用的稳定性和响应速度。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。