https://blog.coding.net/blog/java-coding-performance

1 Visual VM

项目中的某一个接口,在某一场景下(数据量大),性能让人难以忍受。

那么如何有什么工具可以定位引发性能问题的代码呢?其实有很多,这里我们使用 Visual VM。

Visual VM 是一款用来分析 Java 应用的图形工具,能够对 Java 应用程序做性能分析和调优。如果你使用的 java 7 或者 java 8,那么可以直接在 JDK 的 bin 目录找到该工具,名称为 jvisualvm。当然也可以在官网上自行下载。

使用 Visual VM 分析某个接口的性能的方法如下:

结果显示如下:

通过上图,我们可以看到比较耗时的方法为 resolveBytePosition 和 rest,getFile 和 currentUser 是网络请求,暂不考虑。

2 优化一

2.1 背景

首先拿 resolveBytePosition 方法开刀。为了能更容易的解释 resolveBytePosition 的用途,举个例子。

给定一个字符串 chars 与该字符串的 UTF-8 二进制数组(空格用来隔开字符数据,实际并不存在):

chars = "just一个test";
bytes = "6A 75 73 74 E4B880 E4B8AA 74 65 73 74";

resolveBytePosition 用来解决给定一个 bytes 的偏移 bytePos 计算 chars 中的偏移 charPos 的问题。比如:

bytePos = 0 (6A) 对应 charPos = 0 (j)
bytePos = 1 (75) 对应 charPos = 1 (u)

如果使用 array[start:] 表示从下标 start 开始截取数组元素至末尾组成的新数组,那么则有:

bytes[bytePos:] = chars[charPos:]

举例:

bytes[0:] = chars[0:]
bytes[1:] = chars[1:]
bytes[10:] = chars[6:]

2.2 原实现

明白了 resolveBytePosition 的作用,看一下它的实现

public int resolveBytePosition(byte[] bytes, int bytePos) {
return new String(slice(bytes, 0, bytePos)).length();
}

该解法简单粗暴,能够准确的计算出结果,但是缺点显而易见,频繁的构建字符串,对性能造成了极大的影响。通过 Visual VM 可以证实我们的推论,通过点击快照,查看更详细的方法调用耗时。

2.3 剖析

为了更方便的剖析问题,我们绘制如下表格,用来展示每一个字符的 UTF-8 以及 Unicode 的二进制数据:

  j u s t t e s t
UTF-8 6A 75 73 74 E4B880 E4B8AA 74 65 73 74
Unicode 6A 75 73 74 4E00 4E2A 74 65 73 74

接着我们将字节数据转换为字节长度:

  j u s t t e s t
UTF-8 1 1 1 1 3 3 1 1 1 1
Unicode 1 1 1 1 2 2 1 1 1 1

Java中的使用 char 来表示Unicode,char 的长度为 2 个字节,因此一个 char 足以表示示例中的任何一个字符。

我们使用一个单元格表示一个byte(UTF-8)或一个char(Unicode),并对单元格编号,得到下表:

  j u s t t e s t
bytes 0 1 2 3 4 5 6 7 8 9 10 11 12 13
chars 0 1 2 3 4 5 6 7 8 9

可以得出下面对应关系

bytes[0:] = chars[0:]
bytes[1:] = chars[1:]
bytes[2:] = chars[2:]
bytes[3:] = chars[3:]
bytes[4:] = chars[4:]
bytes[7:] = chars[5:]
bytes[10:] = chars[6:]
... ...

2.4 方案

进行到这一步,高效的算法已经呼之欲出了。算法如下:

把字符 UTF-8 数据的二进制长度不为 1 的称为特征点。除特征点外,每个字符都是一个字节长度。记下所有特征点的对应关系,对于给定的 bytePos,都可以根据公式计算得到 charPos。

公式为:

charPos = bytePos - preBytePos + preCharPos

举例:

则本实例中有两个特征点 ,记作:

bytes[6:] = chars[4:]
bytes[9:] = chars[5:]

如果给定 bytePos 10, 首先找到前一个特征点的对应关系 9(preBytePos) -> 5(preCharPos), 根据公式得出 (10 - 9) + 5 = 6。

2.5 核心代码

该算法还有一个比较关键的问题要解决,即高效的计算一个 char 的字节长度。计算 char 的字节长度的算法参考了 StackOverflow

// 计算特征点
private int[][] calcSpecialPos(String str) {
ArrayList<int[]> specialPos = new ArrayList<>() specialPos.add(new int[] {0, 0}); int lastCharPost = 0;
int lastBytePos = 0; Charset utf8 = Charset.forName("UTF-8");
CharsetEncoder encoder = utf8.newEncoder();
CharBuffer input = CharBuffer.wrap(str.toCharArray());
ByteBuffer output = ByteBuffer.allocate(10); int limit = input.limit();
while(input.position() < limit) {
output.clear();
input.mark();
input.limit(Math.min(input.position() + 2, input.capacity()));
if (Character.isHighSurrogate(input.get()) && !Character.isLowSurrogate(input.get())) {
//Malformed surrogate pair
lastCharPost++;
}
input.limit(input.position());
input.reset();
encoder.encode(input, output, false); int encodedLen = output.position();
lastCharPost++;
lastBytePos += encodedLen; if (encodedLen != 1) { // 特征点
specialPos.add(new int[]{lastBytePos, lastCharPost});
}
} return toArray(specialPos);
} // 根据特征点,计算 bytePos 对应的 charPos
private int calcPos(int[][] specialPos, int bytePos) {
// 如果只有一个元素 {0, 0),说明没有特征值
if (specialPos.length == 1) return bytePos; int pos = Arrays.binarySearch(specialPos,
new int[] {bytePos, 0},
(int[] a, int[] b) -> Integer.compare(a[0], b[0])); if (pos >= 0) {
return specialPos[pos][1];
} else {
// if binary search not fonund, will return (-(insertion point) - 1),
// so here -2 is mean -1 to get insertpoint and then -1 to get previous specialPos
int[] preSpecialPos = specialPos[-pos-2];
return bytePos - preSpecialPos[0] + preSpecialPos[1];
}
}

3 优化二

3.1 背景

接下来解决第二个函数 rest。该函数的功能是得到 JsonArray(gson) 的除第一个元素外的所有元素。

由于 rest 是在一个递归函数中被调用且递归栈很深,因此如果 rest 实现的不够高效,其影响会被成倍放大。

3.2 原实现

private JsonArray rest(JsonArray arr) {
JsonArray result = new JsonArray();
if (arr.size() > 1) {
for (int i = 1; i < arr.size(); i++) {
result.add(arr.get(i));
}
}
return result;
}

3.3 剖析

通过调试发现 JsonArray 中存储了相当大的数据,对于频繁调用的场景,每次都对其重新构建明显不是一个明智的选择。
通过查看返回的 JsonArray 使用情况,我们得到了另一条线索:仅仅使用里面的数据,而不涉及修改。

考虑到 JsonArray 被实现成 final,最后方案确定为实现一个针对 rest 这种需求定制的代理类。

3.4 方案 & 代码

代理类 JsonArrayWrapper 分别对 first、rest、foreach 等功能进行了实现。

class JsonArrayWrapper implements Iterable<JsonElement> {
private JsonArray jsonArray; private int mark; public JsonArrayWrapper() {
this.jsonArray = new JsonArray();
this.mark = 0;
} public JsonArrayWrapper(JsonArray jsonArray) {
this.jsonArray= jsonArray;
this.mark = 0;
} public JsonArrayWrapper(JsonArray jsonArray, int mark) {
this.jsonArray = jsonArray;
this.mark = mark;
} public JsonObject first() {
return jsonArray.get(mark).getAsJsonObject();
} public JsonArrayWrapper rest() {
return new JsonArrayWrapper(jsonArray, mark+1);
} public int size() {
return jsonArray.size() - mark;
} public JsonElement get(int n) {
return jsonArray.get(mark + n);
} public void add(JsonElement jsonElement) {
jsonArray.add(jsonElement);
} public void addAll(JsonArrayWrapper jsonArrayWrapper) {
jsonArrayWrapper.forEach(this.jsonArray::add);
} @Override
public Iterator<JsonElement> iterator() {
JsonArray jsonarray = new JsonArray();
this.forEach(e -> jsonarray.add(e));
return jsonarray.iterator();
} @Override
public void forEach(Consumer<? super JsonElement> action) {
for (int i=mark; i<jsonArray.size(); i++) {
action.accept(jsonArray.get(i));
}
}
}

4 成果

经过这两个主要的优化,就解决了代码中的性能问题,成果如下图所示:

Java 项目优化实战的更多相关文章

  1. 【C#】项目优化实战

    [C#]项目优化实战 一. 数据库设计 1. 常量的枚举值直接存中文不要存数字(注意是常量,如果显示值可变就不能) 例如:男女,在数据库中不要存1和0,直接存男和女. 这样的好处:读取数据的时候可以避 ...

  2. Java 性能优化实战记录(3)--JVM OOM的分析和原因追查

    前言: C/C++的程序员渴望Java的自由, Java程序员期许C/C++的约束. 其实那里都是围城, 外面的人想进来, 里面的人想出去. 背景: 作为Java程序员, 除了享受垃圾回收机制带来的便 ...

  3. Java 性能优化实战记录(2)---句柄泄漏和监控

    前言: Java不存在内存泄漏, 但存在过期引用以及资源泄漏. (个人看法, 请大牛指正) 这边对文件句柄泄漏的场景进行下模拟, 并对此做下简单的分析.如下代码为模拟一个服务进程, 忽略了句柄关闭, ...

  4. Java 性能优化实战记录(1)---定位并分析耗cpu最多的线程

    1) jps    列出相关的java进程, 以及对应的pid    也可以使用如下命令来尝试    ps aux | grep java --color 2) top -Hp <pid> ...

  5. Github上可以涨薪30k的Java教程和实战项目终于可以免费下载了

    写在前面 大家都知道 Github 是一个程序员福地,这里有各种厉害的开源框架.软件或者教程.这些东西对于我们学习和进步有着莫大的进步,所以我有了这个将 Github 上非常棒的 Java 开源项目整 ...

  6. java web项目优化记录:优化考试系统

    考试系统在进行压力測试时发现,并发量高之后出现了button无反应.试题答案不能写到数据库的问题,于是针对这些核心问题,进行了优化. 数据库方面: Select语句:Select * from TEB ...

  7. Java秒杀系统实战系列~构建SpringBoot多模块项目

    摘要:本篇博文是“Java秒杀系统实战系列文章”的第二篇,主要分享介绍如何采用IDEA,基于SpringBoot+SpringMVC+Mybatis+分布式中间件构建一个多模块的项目,即“秒杀系统”! ...

  8. Java生鲜电商平台-SpringCloud微服务架构高并发参数优化实战

    Java生鲜电商平台-SpringCloud微服务架构高并发参数优化实战 一.写在前面 在Java生鲜电商平台平台中相信不少朋友都在自己公司使用Spring Cloud框架来构建微服务架构,毕竟现在这 ...

  9. Java最新学习线路(基础,源码,项目,实战)

    如需获取以下学习资源请关注公众号:Java编程指南 我们为自学者编程的或初学java的小伙伴们准备了一整套完整的学习资源和文章,还有我自己在自学路上的一些总结和学习线路,希望能帮到小伙伴们,如果有什么 ...

随机推荐

  1. js事件(一)之事件流

    1.事件流定义 事件发生时会在元素节点与根节点之间按照特定的顺序传播,路径所经过的所有节点都会收到该事件,这个传播过程即DOM事件流. 事件传播的顺序对应浏览器的两种事件流模型:捕获型事件流和冒泡型事 ...

  2. iOS开发阶段技能总结

    这是一篇自己平时纪录的笔记... 1.基本的数据结构常识:链表,队列,栈 2.基本的算法:排序,动态规划等常用算法 3.基本的概念,cocoa,各种自带的view的使用. 4.xcode自带的测试:O ...

  3. 第一个ruby程序

    老实说不是很喜欢去讨论ruby和python的对比,似乎总是把两个语言放在对立的位置上,我觉得没有必要,同样是动态语言,同样是解释型脚本语言,很多特性都是互相影响的,语言本身也在不断进化,我们更应该关 ...

  4. How secure FB Messenger is?

    It's reported that FB Messenge is the most secure App for instant messaging service. Let's see if FB ...

  5. 使用VirtualBox进行端口转发 连接数据库

    转自 http://blog.sina.com.cn/s/blog_484d87770102uxi6.html 使用VirtualBox很久了,很多用法都没有深钻,真的是不虚心学习啊.       由 ...

  6. 利用nodejs的cheerio抓取网站数据

    /*引入模块*/ var http = require('http') var url = 'http://www.cnblogs.com/txxt' var cheerio = require('c ...

  7. 从不同方面寻找bug

    从小学生的角度,为了熟能生巧,更好掌握知识,那就需要不断的练习,然而app没有重新开始的功能,要退出再重新进入,很麻烦.还有,如果小学生只想做一类的计算,例如,只想做减法或乘法,那么它就不能满足. 我 ...

  8. make[2]: *** No rule to make target `/root/.pyenv/versions/anaconda3-2.4.0/lib/libpython3.5m.so', needed by `evaluation.so'. Stop.

    当出现No rule to make target ,肯定是Makefile有问题. 有的makefile是脚本生成的,你得看脚本的配置文件对不对. 我的是这个脚本生成的.发现是Pythondir的配 ...

  9. 关于初学者Could not find action or result :No result defined for action com.lyw.action.LoginAction and result success

    解决如下: 将:<package name="struts2" extends="struts-default" >      <action ...

  10. Generics and Collection (2)

    Integer is a subtype of NumberDouble is a subtype of NumberArrayList<E> is a subtype of List&l ...