学习java 用寨板 性能够吗

帮助企业打造成功软件!
热门搜索:
常用Java性能测试工具的分析与对比
常用Java性能测试工具的分析与对比
| 作者: |
10:29:09| 阅读 0次
概述:在Java程序的开发过程中,不可避免地会遇到内存使用、性能瓶颈等问题。Java性能测试工具可以帮助开发人员快速、有效地解决性能问题。目前,市场上的Java性能测试工具种类多样,每一种工具都有哪些优劣呢?本文将对几款常用的Java性能测试工具进行对比,希望可以帮助Java开发人员选择正确的Java性能测试工具。
虽然市场上的 工具有不少,但是基本功能大多相似,本节首先对这些基本功能进行介绍。
遥测(Telemetry):遥测是一种用来查看应用程序运行行为的最简单的方法。通常会有多个视图(View)分别实时地显示 CPU 使用情况、内存使用情况、线程状态以及其他一些有用的信息,以便用户能很快地发现问题的关键所在。
CPU Telemetry 视图一般用于显示整个应用程序的 CPU 使用情况,有些工具还能显示应用程序中每个线程的 CPU 使用情况。
Memory Telemetry 视图一般用于显示堆内存和非堆内存的分配和使用情况。
Garbage Collection Telemetry 视图显示了 JVM 中垃圾收集器的详细信息。
Threads Telemetry 视图一般用于显示当前运行线程的个数、守护进程的个数等信息。
Classes Telemetry 视图一般用于显示已经载入和还没有载入的类的数量。
快照(snapshot):应用程序启动后,profiler 工具开始收集各种执行数据,其中一些数据直接显示在遥测视图中,而另外大部分数据被保存在内部,直到用户要求获取快照,基于这些保存的数据的统计信息才被 显示出来。快照包含了应用程序在一段时间内的执行信息,通常有两种类型的快照:CPU 快照和内存快照。
CPU 快照主要包含了应用程序中函数的调用关系及运行时间,这些信息通常可以在 CPU 快照视图中进行查看。
内存快照则主要包含了内存的分配和使用情况、载入的所有类、存在的对象信息及对象间的引用关系。这些信息通常可以在内存快照视图中进行查看。
CPU分析:CPU Profiling 的主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的 CPU 使用情况。通常有两种方式来显示 CPU Profiling 结果:CPU 遥测和 CPU 快照。
内存分析:内存 Profiling 的主要目的是通过统计内存使用情况检测可能存在的内存泄露问题及确定优化内存使用的方向。通常有两种方式来显示内存 Profiling 结果:内存遥测和内存快照
线程分析:线程 Profiling 主要用于在多线程应用程序中确定内存的问题所在。
Profiling 的启动设置:类似于 eclipse 中 Run 和 Debug 的启动设置,进行 Profiling 之前也需要进行启动设置,包括:profiling 的模式 (CPU profiling 或内存 profiling),信息获取类型(遥测 , 抽样统计或者 BCI ) 等等。
Profiler Preference 设置:主要用于 Profiler 过滤器(选择需要关注的包、类)、取样间隔时间的设置等。
Java剖析工具介绍
接下来,我们一起来认识几款常用的Java性能分析工具。
JProfiler--唯一一款获过奖的Java剖析器,被誉为&最好的Java剖析/测试工具&
JProfiler是唯一一款获奖的Java剖析器。JProfiler直观的能够帮助你解决性能瓶颈,指明你的内存漏洞并解决线程问题。
JProfiler直觉式的GUI让你可以找到性能瓶颈、抓出内存漏失(memory leaks)、并解决执行绪的问题。它让你得以对heap walker作资源回收器的root analysis,可以轻易找出内存漏失;heap快照(snapshot)模式让未被参照(reference)的对象、稍微被参照的对象、或在终结(finalization)队列的对象都会被移除;整合精灵以便剖析浏览器的Java外挂功能。
YourKit Java Profiler--业界领先的 Java 剖析工具
CPU 和内存的剖析历来是很困难的,YourKit 创造出了革命性的剖析工具,应用在研发和生产阶段,为专业的 Java 开发者带来了无比的好处。
YourKit Java Profiler 使 CPU 和内存相关的性能的一系列问题更加容易,它具有自动检测漏洞、分析内存状态的强有力的工具、对象浏览器、作为 JUnit 测试过程一部分对内存进行全面测试等功能,并可与Eclipse、JBuilder、IntelliJ IDEA、NetBeans 和 JDeveloper IDEs 进行集成。
总的来讲,YourKit Java Profiler 是一个目前很受欢迎的 Java Profiler(Java调优器),可以用来分析和监控你的应用程序的性能,从而进行更好地调优。
Java剖析工具对比
是一款比较独立的工具,安装成功后,用户可以直接在 eclipse 中启动 JProfiler,其他所有操作必须回到 JProfiler 工具中进行。因此和 Eclipse 的集成性稍有不足。使用jprofiler最大的好处就是在观察堆栈和虚拟机内存使用上的方便,另外测试多线程也比jdk自带的两个工具强大。
YourKit Java Profiler也是一个比较独立的工具,安装成功后,用户可以直接在 eclipse 中启动 YourKit Java Profiler 并对 profiling 选项进行配置,但是用户必须在 YourKit Java Profiler 工具中对 Profiling preferrence 进行配置,而且 profiling 信息必须在 YourKit Java Profiler中进行查看,因此使用起来不太方便。
慧都控件|提供软件技术整体解决方案
云集全球三千余款优秀控件、软件产品,提供行业领先的咨询、培训与开发服务
企业QQ:|电话:023-
服务与支持
重庆 / 023-
北京 / 010-
400-700-1020
慧都旗下网站
慧都科技有限公司 版权所有 Copyright
100%正版软件
400-700-1020
<input type="hidden" id="url" value=""/>211被浏览8414分享邀请回答4添加评论分享收藏感谢收起// 反射
System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch();
sw1.Start();
for (int n = 0; n & 1000000; n++)
MethodInfo mi = this.GetType().GetMember("foo")[0] as MethodInfo;
mi.Invoke(this, new object[] { 1, "sss" });
sw1.Stop();
Debug.Log(sw1.ElapsedMilliseconds);
// 直接调用
System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
sw2.Start();
for (int n = 0; n & 1000000; n++)
this.foo(1, "sss");
sw2.Stop();
Debug.Log(sw2.ElapsedMilliseconds);
// 创建代理
System.Diagnostics.Stopwatch sw3 = new System.Diagnostics.Stopwatch();
sw3.Start();
MethodInfo mi2 = this.GetType().GetMember("foo")[0] as MethodInfo;
FooDelegate d = System.Delegate.CreateDelegate(typeof(FooDelegate), this,mi2) as FooDelegate;
for (int n = 0; n & 1000000; n++)
d(1, "sss");
sw3.Stop();
Debug.Log(sw3.ElapsedMilliseconds);
// 反射cache版
System.Diagnostics.Stopwatch sw4 = new System.Diagnostics.Stopwatch();
sw4.Start();
MethodInfo mi = this.GetType().GetMember("foo")[0] as MethodInfo;
for (int n = 0; n & 1000000; n++)
mi.Invoke(this, new object[] { 1, "sss" });
sw4.Stop();
Debug.Log(sw4.ElapsedMilliseconds);
foo函数简单如下:
delegate void FooDelegate(int a, string b);
public void foo(int a, string b)
输出为43270ms(反射版)6ms(直接调用版)7ms(创建代理版)1100ms(反射cache版)如果考虑到反射还可能是属性/event/field等, 就变的更复杂了.16添加评论分享收藏感谢收起查看更多回答Ubuntu下使用Java visualVM对Java程序进行性能分析
使用Java visualVM的原因:
  体积小,不耗内存,操作简单,功能全。关于Java visualVM和其他性能测试工具的比较,网上有很多文章,去搜搜看吧。
1. 在服务器上配置jstatd的security policy文件。
使用Java visualVM的原因:
  体积小,不耗内存,操作简单,功能全。关于Java visualVM和其他性能测试工具的比较,网上有很多文章,去搜搜看吧。
1. 在服务器上配置jstatd的security policy文件。
  jstatd是一个监控JVM(Java Virtual Machine,Java虚拟机)从创建到销毁过程中资源占用情况,并提供远程监控接口的RMI(Remote Method Invocation,远程方法调用)服务程序,它是一个Deamon程序,要保证远程监控软件连接到本地的话,需要始终保持jstatd运行。jstatd运行需要通过-J-Djava.security.policy=(某个文件的路径名,这个文件指定了安全策略)。因此我们需要在服务器上建立这个文件,文件可随意起名,这里我取名为jstatd.policy。文件内容必须如下:
grant codebase "file:${java.home}/../lib/tools.jar"{
  permission java.security.AllP
  注意,这里不建议进行复制,建议手敲一遍,避免因为半角和圆角符号导致错误。另外,内容必须如此,否则报错,提示没有权限:
Could not create remote object
  access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")
    java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses"
    at......
2. 修改服务器hosts文件中的IP地址
  要使Java visualVM成功连接到服务器上,服务器端因该在/etc/hosts文件中把本机地址设置为实际的IP地址(比如192.168.3.123),而不是127.0.0.1。可以先使用hostname -i查看,再用vi修改
3. 在用于查看服务器的机器上启动JvisualVM
  1.先安装JvisualVM:
sudo apt-get install jvisualvm
  2.运行JvisualVM,注意这里需要加上Java的路径:
jvisualvm --jdkhome ${JAVA_HOME}
  注意:${JAVA_HOME}是指在Ubuntu系统中JDK的环境变量
4. 在服务器上运行jstatd程序
jstatd -J-Djava.security.policy=jstatd.policy
  注意:这里的jstatd.policy即为刚刚创建的关于安全策略的文件名,还需要指定这个文件的路径,比如:/usr/lib/jstatd.policy
  如果还需要RMI日志功能的话,还可以加上启动参数-J-Djava.rmi.server.logCalls:
jstatd -J-Djava.security.policy=jstatd.policy -J-Djava.rmi.server.logCalls=true
  之后就可以查看这台服务器上的JVM运行情况了
5. 通过Java JvisualVM连接到服务器
  在Java visualVM程序窗口左侧“远程”项目右键选择“添加远程主机”,在弹出的对话框中输入远程主机的IP地址,确认提交以后可以看到相应的主机和运行在上面的Java程序,连接成功后会显示该主机上运行的Jstatd机器PID。
  通过以上方式连接服务器有一个问题,不能监控服务器的CPU使用情况,提示我们建立JMX,查阅资料发现,可以在服务器启动jstatd加入JMX的参数:
jstatd -J-Djava.rmi.server.hostname=192.168.3.123
-J-Djava.security.policy=jstatd.policy
-J-Dcom.sun.management.jmxremote.port=4444 -J-Dcom.sum.management.jmxremote.ssl=false
-J-Dcom.sun.management.jmxremote.authenticate=false
  注意:上述脚本为一句,就是一行,不要自行换行,否则不能启动jstatd。其中:
  -J-Djava.rmi.server.hostname表示服务器的地址;
  -J-Djava.security.policy表示使用的安全策略文件路径;
  -J-Dcom.sun.management.jmxremote.port表示暴露服务器JVM给其他机器的端口号;
-J-Dcom.sum.management.jmxremote.ssl=false表示对RMX传输不加密
  -J-Dcom.sun.management.jmxremote.authenticate=false表示其他机器连接服务器时不需要验证,若为true,则在连接服务器时需要输入密码
  这段脚本会阻塞当前的命令行,如果不想其阻塞,可以在最后打个空格,再加个&符号即可,此时这条命令会被当作作业添加到后台启动:
jstatd -J-Djava.rmi.server.hostname=192.168.3.123
-J-Djava.security.policy=jstatd.policy
-J-Dcom.sun.management.jmxremote.port=4444 -J-Dcom.sum.management.jmxremote.ssl=false
-J-Dcom.sun.management.jmxremote.authenticate=false &
  这样启动后怎么停止?请看这里:
  有了上述步骤后,你就可以对远程服务器的JVM了如指掌了。
版权声明:本文内容由互联网用户自发贡献,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至: 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
用云栖社区APP,舒服~
【云栖快讯】他,一路保送,但可能不是你想象中的学霸; 他,曾是微软最年轻的技术管理者,挑战带领跨国团队; 他,后来加入阿里,成为阿里西雅图分部的第22名员工;
他,就是阿里通用计算平台负责人关涛! 云栖社区通过短视频,为你揭晓他的成长和开发计算平台的经历,以及他对未来的展望!&&
一款阿里巴巴自主研发的高性能、分布式的关系型数据库,支持完整的ACID特性。它高度兼容MySQL协议与语法,让用...
共享带宽提供地域级带宽共享和复用功能,支持同地域下所有弹性公网IP共享带宽,进而让绑定弹性公网IP的云服务器EC...
对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系...
为您提供简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效率,降低 IT 成本...
社区之星年度评选,投票可抽奖
Loading...为什么使用final可以提高,java程序的性能?_百度知道
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。
为什么使用final可以提高,java程序的性能?
我有更好的答案
final的话说明此变量的值不能被改变,java虚拟机对具有final修饰符的变量具有特殊的优化,所以会提高一点性能,但是这种性能的提升非常的有限,不能作为优化效率的一种方法
采纳率:19%
这是java虚拟机内部决定的 final就不能再继承了 是最终的了
final是最终的,不能修改,其放在不可修改内存局域,访问效率较高
是的,final是最终的类,不能再继承
其他1条回答
为您推荐:
其他类似问题
您可能关注的内容
java程序的相关知识
等待您来回答10种简单的Java性能优化 - ImportNew
你是否正打算优化hashCode()方法?是否想要绕开正则表达式?Lukas Eder介绍了很多简单方便的性能优化小贴士以及扩展程序性能的技巧。
最近“全网域()”一词被炒得火热,人们也正在通过扩展他们的应用程序架构来使他们的系统变得更加“全网域”。但是究竟什么是全网域?或者说如何确保全网域?
扩展的不同方面
全网域被炒作的最多的是扩展负载(Scaling load),比如支持单个用户访问的系统也可以支持10 个、100个、甚至100万个用户访问。在理想情况下,我们的系统应该保持尽可能的“无状态化(stateless)”。即使必须存在状态,也可以在网络的不同处理终端上转化并进行传输。当负载成为瓶颈时候,可能就不会出现延迟。所以对于单个请求来说,耗费50到100毫秒也是可以接受的。这就是所谓的横向扩展(Scaling out)。
扩展在全网域优化中的表现则完全不同,比如确保成功处理一条数据的算法也可成功处理10条、100条甚至100万条数据。无论这种度量类型是是否可行,事件复杂度(大O符号)是最佳描述。延迟是性能扩展杀手。你会想尽办法将所有的运算处理在同一台机器上进行。这就是所谓的纵向扩展(Scaling up)。
如果天上能掉馅饼的话(当然),我们或许能把横向扩展和纵向扩展组合起来。但是,今天我们只打算介绍下面几条提升效率的简单方法。
和Java8 的并行数据流() 都对并行处理有所帮助。当在多核处理器上部署Java程序时表现尤为明显,因所有的处理器都可以访问相同的内存。
所以,这种并行处理较之在跨网络的不同机器上进行扩展,根本的好处是几乎可以完全消除延迟。
但不要被并行处理的效果所迷惑!请谨记下面两点:
并行处理会吃光处理器资源。并行处理为批处理带来了极大的好处,但同时也是非同步服务器(如HTTP)的噩梦。有很多原因可以解释,为什么在过去的几十年中我们一直在使用单线程的Servlet模型。并行处理仅在纵向扩展时才能带来实际的好处。
并行处理对算法复杂度没有影响。如果你的算法的时间复杂度为 O(nlogn),让算法在 c 个处理器上运行,事件复杂度仍然为 O(nlogn/c), 因为 c 只是算法中的一个无关紧要的常量。你节省的仅仅是时钟时间(wall-clock time),实际的算法复杂度并没有降低。
降低算法复杂度毫无疑问是改善性能最行之有效的办法。比如对于一个 HashMap 实例的 lookup() 方法来说,事件复杂度 O(1) 或者空间复杂度 O(1) 是最快的。但这种情况往往是不可能的,更别提轻易地实现。
如果你不能降低算法的复杂度,也可以通过找到算法中的关键点并加以改善的方法,来起到改善性能的作用。假设我们有下面这样的算法示意图:
该算法的整体时间复杂度为 O(N3),如果按照单独访问顺序计算也可得出复杂度为 O(N x O x P)。但是不管怎样,在我们分析这段代码时会发现一些奇怪的场景:
在开发环境中,通过测试数据可以看到:左分支(N-&M-&Heavy operation)的时间复杂度 M 的值要大于右边的 O 和 P,所以在我们的分析器中仅仅看到了左分支。
在生产环境中,你的维护团队可能会通过 、或其它小工具发现,真正导致问题的罪魁祸首是右分支(N -& O -& P -& Easy operation or also N.O.P.E.)。
在没有生产数据参照的情况下,我们可能会轻易的得出要优化“高开销操作”的结论。但我们做出的优化对交付的产品没有起到任何效果。
优化的金科玉律不外乎以下内容:
良好的设计将会使优化变得更加容易。
过早的优化并不能解决多有的性能问题,但是不良的设计将会导致优化难度的增加。
理论就先谈到这里。假设我们已经发现了问题出现在了右分支上,很有可能是因产品中的简单处理因耗费了大量的时间而失去响应(假设N、O和 P 的值非常大), 请注意文章中提及的左分支的时间复杂度为 O(N3)。这里所做出的努力并不能扩展,但可以为用户节省时间,将困难的性能改善推迟到后面再进行。
这里有10条改善Java性能的小建议:
1、使用StringBuilder
StingBuilder 应该是在我们的Java代码中默认使用的,应该避免使用 + 操作符。或许你会对 StringBuilder 的语法糖(syntax sugar)持有不同意见,比如:
String x = &a& + args.length + &b&;
将会被编译为:
new java.lang.StringBuilder [16]
ldc &String &a&& [18]
invokespecial java.lang.StringBuilder(java.lang.String) [20]
aload_0 [args]
arraylength
invokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [23]
ldc &String &b&& [27]
invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [29]
invokevirtual java.lang.StringBuilder.toString() : java.lang.String [32]
astore_1 [x]
但究竟发生了什么?接下来是否需要用下面的部分来对 String 进行改善呢?
String x = &a& + args.length + &b&;
if (args.length == 1)
x = x + args[0];
现在使用到了第二个 StringBuilder,这个 StringBuilder 不会消耗堆中额外的内存,但却给 GC 带来了压力。
StringBuilder x = new StringBuilder(&a&);
x.append(args.length);
x.append(&b&);
if (args.length == 1);
x.append(args[0]);
在上面的样例中,如果你是依靠Java编译器来隐式生成实例的话,那么编译的效果几乎和是否使用了 StringBuilder 实例毫无关系。请记住:在
N.O.P.E 分支中,每次CPU的循环的时间到白白的耗费在GC或者为 StringBuilder 分配默认空间上了,我们是在浪费 N x O x P 时间。
一般来说,使用 StringBuilder 的效果要优于使用 + 操作符。如果可能的话请在需要跨多个方法传递引用的情况下选择 StringBuilder,因为 String 要消耗额外的资源。JOOQ在生成复杂的SQL语句便使用了这样的方式。在整个)SQL传递过程中仅使用了一个 StringBuilder 。
更加悲剧的是,如果你仍在使用
的话,那么用 StringBuilder 代替 吧,毕竟需要同步字符串的情况真的不多。
2、避免使用正则表达式
正则表达式给人的印象是快捷简便。但是在 N.O.P.E 分支中使用正则表达式将是最糟糕的决定。如果万不得已非要在计算密集型代码中使用正则表达式的话,至少要将 缓存下来,避免反复编译Pattern。
static final Pattern HEAVY_REGEX =
pile(&(((X)*Y)*Z)*&);
如果仅使用到了如下这样简单的正则表达式的话:
String[] parts = ipAddress.split(&\\.&);
这是最好还是用普通的 char[] 数组或者是基于索引的操作。比如下面这段可读性比较差的代码其实起到了相同的作用。
int length = ipAddress.length();
int offset = 0;
int part = 0;
for (int i = 0; i & i++) {
if (i == length - 1 ||
ipAddress.charAt(i + 1) == &#039;.&#039;) {
parts[part] =
ipAddress.substring(offset, i + 1);
offset = i + 2;
上面的代码同时表明了过早的优化是没有意义的。虽然与 split() 方法相比较,这段代码的可维护性比较差。
挑战:聪明的小伙伴能想出更快的算法吗?
正则表达式是十分有用,但是在使用时也要付出代价。尤其是在 N.O.P.E 分支深处时,要不惜一切代码避免使用正则表达式。还要小心各种使用到正则表达式的JDK字符串方法,比如
或 。可以选择用比较流行的开发库,比如
来进行字符串操作。
3、不要使用iterator()方法
这条建议不适用于一般的场合,仅适用于在 N.O.P.E 分支深处的场景。尽管如此也应该有所了解。Java 5格式的循环写法非常的方便,以至于我们可以忘记内部的循环方法,比如:
for (String value : strings) {
// Do something useful here
当每次代码运行到这个循环时,如果 strings 变量是一个
的话,代码将会自动创建一个 的实例。如果使用的是
的话,虚拟机会自动在堆上为对象分配3个整数类型大小的内存。
private class Itr implements Iterator&E& {
int lastRet = -1;
int expectedModCount = modC
也可以用下面等价的循环方式来替代上面的 for 循环,仅仅是在栈上“浪费”了区区一个整形,相当划算。
int size = strings.size();
for (int i = 0; i & i++) {
String value : strings.get(i);
// Do something useful here
如果循环中字符串的值是不怎么变化,也可用数组来实现循环。
for (String value : stringArray) {
// Do something useful here
无论是从易读写的角度来说,还是从API设计的角度来说迭代器、Iterable接口和 foreach 循环都是非常好用的。但代价是,使用它们时是会额外在堆上为每个循环子创建一个对象。如果循环要执行很多很多遍,请注意避免生成无意义的实例,最好用基本的指针循环方式来代替上述迭代器、Iterable接口和 foreach 循环。
一些与上述内容持反对意见的看法(尤其是用指针操作替代迭代器)详见。
4、不要调用高开销方法
有些方法的开销很大。以 N.O.P.E 分支为例,我们没有提到叶子的相关方法,不过这个可以有。假设我们的JDBC驱动需要排除万难去计算
方法的返回值。我们自己实现的SQL框架可能像下面这样:
if (type == Integer.class) {
result = (T) wasNull(rs,
Integer.valueOf(rs.getInt(index)));
// And then...
static final &T& T wasNull(ResultSet rs, T value)
throws SQLException {
return rs.wasNull() ? null :
在上面的逻辑中,每次从结果集中取得 int 值时都要调用 ResultSet.wasNull() 方法,但是 getInt() 的方法定义为:
返回类型:变量值;如果SQL查询结果为NULL,则返回0。
所以一个简单有效的改善方法如下:
static final &T extends Number& T wasNull(
ResultSet rs, T value
throws SQLException {
return (value == null ||
(value.intValue() == 0 && rs.wasNull()))
这是轻而易举的事情。
将方法调用缓存起来替代在叶子节点的高开销方法,或者在方法约定允许的情况下避免调用高开销方法。
5、使用原始类型和栈
上面介绍了来自 的例子中使用了大量的泛型,导致的结果是使用了 byte、 short、 int 和 long 的包装类。但之前,不应该成为代码的限制。因为可以通过下面的方法来进行替换:
//存储在堆上
Integer i = 817598;
……如果这样写的话:
// 存储在栈上
int i = 817598;
在使用数组时情况可能会变得更加糟糕:
//在堆上生成了三个对象
Integer[] i = {
……如果这样写的话:
// 仅在堆上生成了一个对象
int[] i = {
当我们处于 N.O.P.E. 分支的深处时,应该极力避免使用包装类。这样做的坏处是给GC带来了很大的压力。GC将会为清除包装类生成的对象而忙得不可开交。
所以一个有效的优化方法是使用基本数据类型、定长数组,并用一系列分割变量来标识对象在数组中所处的位置。
遵循LGPL协议的 是一个Java集合类库,它为我们提供了优于整形数组 int[] 更好的性能实现。
下面的情况对这条规则例外:因为 boolean 和 byte 类型不足以让JDK为其提供缓存方法。我们可以这样写:
Boolean a1 = // ... syntax sugar for:
Boolean a2 = Boolean.valueOf(true);
Byte b1 = (byte) 123; // ... syntax sugar for:
Byte b2 = Byte.valueOf((byte) 123);
其它整数基本类型也有类似情况,比如 char、short、int、long。
不要在调用构造方法时将这些整型基本类型自动装箱或者调用 TheType.valueOf() 方法。
也不要在包装类上调用构造方法,除非你想得到一个不在堆上创建的实例。这样做的好处是。
当然了,如果你还想体验下堆外函数库的话,尽管这可能参杂着不少战略决策,而并非最乐观的本地方案。一篇由Peter Lawrey和 Ben Cotton撰写的关于非堆存储的很有意思文章请点击: 。
6、避免递归
现在,类似Scala这样的函数式编程语言都鼓励使用递归。因为递归通常意味着能分解到单独个体优化的尾递归(tail-recursing)。如果你使用的编程语言能够支持那是再好不过。不过即使如此,也要注意对算法的细微调整将会使尾递归变为普通递归。
希望编译器能自动探测到这一点,否则本来我们将为只需使用几个本地变量就能搞定的事情而白白浪费大量的堆栈框架(stack frames)。
这节中没什么好说的,除了在 N.O.P.E 分支尽量使用迭代来代替递归。
7、使用entrySet()
当我们想遍历一个用键值对形式保存的 时,必须要为下面的代码找到一个很好的理由:
for (K key : map.keySet()) {
V value : map.get(key);
更不用说下面的写法:
for (Entry&K, V& entry : map.entrySet()) {
K key = entry.getKey();
V value = entry.getValue();
在我们使用 N.O.P.E. 分支应该慎用map。因为很多看似时间复杂度为 O(1) 的访问操作其实是由一系列的操作组成的。而且访问本身也不是免费的。至少,如果不得不使用map的话,那么要用
方法去迭代!这样的话,我们要访问的就仅仅是Map.Entry的实例。
在需要迭代键值对形式的Map时一定要用 entrySet() 方法。
8、使用EnumSet或EnumMap
在某些情况下,比如在使用配置map时,我们可能会预先知道保存在map中键值。如果这个键值非常小,我们就应该考虑使用 EnumSet 或 EnumMap,而并非使用我们常用的 HashSet 或 HashMap。下面的代码给出了很清楚的解释:
private transient Object[]
public V put(K key, V value) {
int index = key.ordinal();
vals[index] = maskNull(value);
上段代码的关键实现在于,我们用数组代替了哈希表。尤其是向map中插入新值时,所要做的仅仅是获得一个由编译器为每个枚举类型生成的常量序列号。如果有一个全局的map配置(例如只有一个实例),在增加访问速度的压力下,EnumMap 会获得比 HashMap 更加杰出的表现。原因在于 EnumMap 使用的堆内存比 HashMap 要少 一位(bit),而且 HashMap 要在每个键值上都要调用 hashCode() 方法和 equals() 方法。
Enum 和 EnumMap 是亲密的小伙伴。在我们用到类似枚举(enum-like)结构的键值时,就应该考虑将这些键值用声明为枚举类型,并将之作为 EnumMap 键。
9、优化自定义hasCode()方法和equals()方法
在不能使用EnumMap的情况下,至少也要优化 hashCode() 和 equals() 方法。一个好的 hashCode() 方法是很有必要的,因为它能防止对高开销 equals() 方法多余的调用。
在每个类的继承结构中,需要容易接受的简单对象。让我们看一下jOOQ的
是如何实现的?
最简单、快速的 hashCode() 实现方法如下:
// AbstractTable一个通用Table的基础实现:
public int hashCode() {
// [#1938] 与标准的QueryParts相比,这是一个更加高效的hashCode()实现
return name.hashCode();
name即为表名。我们甚至不需要考虑schema或者其它表属性,因为表名在数据库中通常是唯一的。并且变量 name 是一个字符串,它本身早就已经缓存了一个 hashCode() 值。
这段代码中注释十分重要,因继承自 AbstractQueryPart 的 AbstractTable 是任意抽象语法树元素的基本实现。普通抽象语法树元素并没有任何属性,所以不能对优化 hashCode() 方法实现抱有任何幻想。覆盖后的 hashCode() 方法如下:
// AbstractQueryPart一个通用抽象语法树基础实现:
public int hashCode() {
// 这是一个可工作的默认实现。
// 具体实现的子类应当覆盖此方法以提高性能。
return create().renderInlined(this).hashCode();
换句话说,要触发整个SQL渲染工作流程(rendering workflow)来计算一个普通抽象语法树元素的hash代码。
equals() 方法则更加有趣:
// AbstractTable通用表的基础实现:
public boolean equals(Object that) {
if (this == that) {
// [#2144] 在调用高开销的AbstractQueryPart.equals()方法前,
// 可以及早知道对象是否不相等。
if (that instanceof AbstractTable) {
if (StringUtils.equals(name,
(((AbstractTable&?&) that).name))) {
return super.equals(that);
首先,不要过早使用 equals() 方法(不仅在N.O.P.E.中),如果:
this == argument
this“不兼容:参数
注意:如果我们过早使用 instanceof 来检验兼容类型的话,后面的条件其实包含了argument == null。我在以前的博客中已经对这一点进行了说明,请参考。
在我们对以上几种情况的比较结束后,应该能得出部分结论。比如jOOQ的 Table.equals() 方法说明是,用来比较两张表是否相同。不论具体实现类型如何,它们必须要有相同的字段名。比如下面两个元素是不可能相同的:
com.example.generated.Tables.MY_TABLE
DSL.tableByName(&#8220;MY_OTHER_TABLE&#8221;)
如果我们能方便地判断传入参数是否等于实例本身(this),就可以在返回结果为 false 的情况下放弃操作。如果返回结果为 true,我们还可以进一步对父类(super)实现进行判断。在比较过的大多数对象都不等的情况下,我们可以尽早结束方法来节省CPU的执行时间。
一些对象的相似度比其它对象更高。
在jOOQ中,大多数的表实例是由jOOQ的代码生成器生成的,这些实例的 equals() 方法都经过了深度优化。而数十种其它的表类型(衍生表 (derived tables)、表值函数(table-valued functions)、数组表(array tables)、连接表(joined tables)、数据透视表(pivot tables)、公用表表达式(common table expressions)等,则保持 equals() 方法的基本实现。
10、考虑使用set而并非单个元素
最后,还有一种情况可以适用于所有语言而并非仅仅同Java有关。除此以外,我们以前研究的 N.O.P.E. 分支也会对了解从 O(N3) 到 O(n log n)有所帮助。
不幸的是,很多程序员的用简单的、本地算法来考虑问题。他们习惯按部就班地解决问题。这是命令式(imperative)的“是/或”形式的函数式编程风格。这种编程风格在由纯粹命令式编程向面对象式编程向函数式编程转换时,很容易将“更大的场景(bigger picture)”模型化,但是这些风格都缺少了只有在SQL和R语言中存在的:
声明式编程。
在SQL中,我们可以在不考虑算法影响下声明要求数据库得到的效果。数据库可以根据数据类型,比如约束(constraints)、键(key)、索引(indexes)等不同来采取最佳的算法。
在理论上,我们最初在SQL和关系演算(relational calculus)后就有了基本的想法。在实践中,SQL的供应商们在过去的几十年中已经实现了基于开销的高效优化器 。然后到了2010版,我们才终于将SQL的所有潜力全部挖掘出来。
但是我们还不需要用set方式来实现SQL。所有的语言和库都支持Sets、collections、bags、lists。使用set的主要好处是能使我们的代码变的简洁明了。比如下面的写法:
SomeSet INTERSECT SomeOtherSet
// Java 8以前的写法
Set result = new HashSet();
for (Object candidate : someSet)
if (someOtherSet.contains(candidate))
result.add(candidate);
// 即使采用Java 8也没有很大帮助
someSet.stream()
.filter(someOtherSet::contains)
.collect(Collectors.toSet());
有些人可能会对函数式编程和Java 8能帮助我们写出更加简单、简洁的算法持有不同的意见。但这种看法不一定是对的。我们可以把命令式的Java 7循环转换成Java 8的Stream collection,但是我们还是采用了相同的算法。但SQL风格的表达式则是不同的:
SomeSet INTERSECT SomeOtherSet
上面的代码在不同的引擎上可以有1000种不同的实现。我们今天所研究的是,在调用 INTERSECT 操作之前,更加智能地将两个set自动的转化为 EnumSet 。甚至我们可以在不需要调用底层的
方法的情况下进行并行 INTERSECT 操作。
在这篇文章中,我们讨论了关于N.O.P.E.分支的优化。比如深入高复杂性的算法。作为jOOQ的开发者,我们很乐于对SQL的生成进行优化。
每条查询都用唯一的StringBuilder来生成。
模板引擎实际上处理的是字符而并非正则表达式。
选择尽可能的使用数组,尤其是在对监听器进行迭代时。
对JDBC的方法敬而远之。
jOOQ处在“食物链的底端”,因为它是在离开JVM进入到DBMS时,被我们电脑程序所调用的最后一个API。位于食物链的底端意味着任何一条线路在jOOQ中被执行时都需要 N x O x P 的时间,所以我要尽早进行优化。
我们的业务逻辑可能没有N.O.P.E.分支那么复杂。但是基础框架有可能十分复杂(本地SQL框架、本地库等)。所以需要按照我们今天提到的原则,用 或其它工具进行复查,确认是否有需要优化的地方。
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
推荐的书都挺不错的,我也买了一堆,就是难静下心来看
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
&#8211; 好的话题、有启发的回复、值得信赖的圈子
&#8211; 写了文章?看干货?去头条!
&#8211; 为IT单身男女服务的征婚传播平台
&#8211; 优秀的工具资源导航
&#8211; 活跃 &#038; 专业的翻译小组
&#8211; 国内外的精选博客文章
&#8211; UI,网页,交互和用户体验
&#8211; JavaScript, HTML5, CSS
&#8211; 专注Android技术分享
&#8211; 专注iOS技术分享
&#8211; 专注Java技术分享
&#8211; 专注Python技术分享
& 2017 ImportNew

我要回帖

 

随机推荐