一次Linux环境下的Java字符串性能问题定位及修复

问题描述:利用tcp发送Base64编码的文件时,出现大于1.5m左右的文件无法上送的问题,起初认为为系统架构原因所导致的问题,故一直在base64的字符串传输及文件转为base64的代码处查找原因,但无论如何更改,问题依旧。经过冷静思考后决定仔细地复现问题,将整个业务过程所调用的函数逐一抽出,在mian函数处按业务顺序调用,完成demo后放到Linux上执行,在1m的文件下,很快就执行完成了,但是,大于15m的文件,程序出现一直卡死状态。

想起之前用top命令处理过一个死循环问题,故用top尝试一下系统的情况,脚本为: top ,如图:
由于调试过程没有截图,故采用网络图片
注意:由于调试过程没有截图,故采用网络图片,可见,Java的cpu高达172.9,说明程序一直占用cpu时钟,几乎处于卡死状态,然后然后我们一步步来找到占用cpu的代码。
查找进程733下的线程,脚本为: ps -mp 733 -o THREAD,tid,time | sort -rn ,如图:
由于调试过程没有截图,故采用网络图片
注意:由于调试过程没有截图,故采用网络图片,可以看到TID 线程775占用了96%且持有了很长时间 其实到这一步基本上能猜测到应该是:肯定是那段代码发生了死循环。
接下来要找出运行线程775的Java代码,首先将线程ID转换为16进制格式,脚本为: printf “%x\n” 775 ,如图:
由于调试过程没有截图,故采用网络图片
注意:由于调试过程没有截图,故采用网络图片,然后利用JStack找出线程775对应的代码栈信息,脚本: jstack 733 |grep 307 -A 30 ,如图:
由于调试过程没有截图,故采用网络图片
注意:由于调试过程没有截图,故采用网络图片,根据输出的栈信息,我们找到对应的类及行,为一个将普通字符串转为16进制字符串的函数:

	/**
	 * 字符串文本转化为 每个字符对应的十六进制(含英文字符串、中文字符串)
	 * 
	 * @param str        input
	 * @param charEncode 编码
	 * @return 转码结果
	 */
	public static String StringTo16HexString(String str, String charEncode) throws Exception {
		if (str == null || str.equals("")) {
			throw new Exception("str can not be empty");
		}
		byte[] bt = null;
		bt = str.getBytes(charEncode);
		String result = "";
		for (int i = 0; i < bt.length; i++) {
			String tempStr = Integer.toHexString(bt[i]);
			if (tempStr.length() > 2) {
				tempStr = tempStr.substring(tempStr.length() - 2);
			}
			result = result + tempStr + "";
		}
		return result.toUpperCase(Locale.ENGLISH);
	}

居然在一个字符拼接函数上出现String + String + String 这种方式,不卡死才怪呢。肯定是之前从网络上的demo copy字符转换函数代码的时候,没有注意实现细节,只测试过功能就粘贴过来了。根据经验,将 “+” 这种拼接方式改为StringBuilder 的方式,并把StringBuilder写在for外面,如图:

	/**
	 * 字符串文本转化为 每个字符对应的十六进制(含英文字符串、中文字符串)
	 * 
	 * @param str        input
	 * @param charEncode 编码
	 * @return 转码结果
	 */
	public static String StringTo16HexString(String str, String charEncode) throws Exception {
		if (str == null || str.equals("")) {
			throw new Exception("str can not be empty");
		}
		byte[] bt = null;
		bt = str.getBytes(charEncode);
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < bt.length; i++) {
			String tempStr = Integer.toHexString(bt[i]);
			if (tempStr.length() > 2) {
				tempStr = tempStr.substring(tempStr.length() - 2);
			}
			sb.append(tempStr);
		}
		return sb.toString().toUpperCase(Locale.ENGLISH);
	}

重新执行demo,运行超快,问题解决。

总结:
1.定位问题时,尽量采用Debug方式,但是性能问题采用Debug很难查出的,特别是在Linux环境的时候,可以采用top + jstack的方式定位处消耗cpu资源的代码。
2.很多时候,功能有问题是因为个别函数代码的性能问题引起的,由于项目为多线程,该模块卡死并没有引起程序整体卡死,之前一直没有往该方向上定位原因,以后若出现相似问题时,应该多往该方向想想。
3.尽量不要用“+”来拼接字符,因为,“+”的实现就时new一个StringBuilder来append字符串的,如果拼接较多的情况下,会产生大量的StringBuilder对象,从而引起程序出现性能问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值