一点数据库访问优化的思考

最近碰到了老是数据库连接池爆满的情况,于是调研了下应用中的代码存在哪些问题,哪些地方可以优化,于是又了下面的一点思路,欢迎抛砖。

核心思路是:减少单页面请求数据库的sql数量,缩短单url请求数据库的时间。

平时开发时养成性能优化的思维模式能大大减少后期性能优化的成本,因为开发过程中,很多业务和实现细节了然于胸,如果此时就考虑性能优化,就能始终保持最大化整个应用的性能(当然前提是,如果你没那么急的话)。

数据库访问优化
数据库访问优化

 

 

 

fastjson解析大文件时抛出java.lang.ArrayIndexOutOfBoundsException: -1

最近发现偶尔出现fastjson解析大文件时抛出java.lang.ArrayIndexOutOfBoundsException: -1异常。可以我是按照官方的中的实例代码来写的,如下的代码:

所以非常不解,我自己单独写代码解析json文件又没有问题,在应用跑了一段时间后,就突然出现这个问题。由于fastjson的代码没有深入阅读,而且异常还不是每次都能碰到,因此没有查到直接原因。但同事发现换个api来解析就不抛出异常了,因此在这里记录下,下次朋友类似的情况,也可以按照种方式来解决。代码这样改:

可能这种改法效率没有上面那个好,但是这个确实没有抛出异常了,至少在可用性方面得到保证。如果你也碰到这种情况,也可以尝试这样修改你代码。

【译】编写自定义的Velocity指令

本文将使用Apache Velocity API来编写自己的行内指令和块指令。以实现truncate()为例,即实现一个velocity的展示工具方法,用来对长字符串做截断。

在velocity中有两种指令:行内指令和块指令。行内指令在模板中仅有一行vm代码,而块指令有一段代码块同时以#end标签结尾。如下面所示的,第一个为行内指令,第二个为块指令:

编写行指令

为了声明你自定义的指令,你需要在velocity配置文件(比如:velocity.properties)中指定userdirective参数值,如果有多个自定义指令,则需要使用逗号来分割多个自定义指令类的全名。类似下面这样:

下面来创建行内指令#truncate,该指令有4个参数,如下:

第一个参数是需要被截断的字符串,而且是为一个必须的参数。其他几个参数都是用来对截断进行设置的可选参数。具体指令代码如下:

在Apache中所有的指令都继承自Directive 类,它有三个抽象方法需要我们来实现:getName()getType()render()

  • 第一个方法getName() 应该返回指令的名称,该名称将在模板中使用,即#后面的指令名称。
  • 第二个方法getType() 返回 BLOCK 或者 LINE,由你所实现的指令类型决定。
  • 第三个方法render(InternalContextAdapter context, Writer writer, Node node) 是真正起到作用的代码。Writer 是用来写入结果对象的;Node 对象包含了我们的指令信息(指令参数和属性);InternalContextAdapter 包含了Velocity渲染模板所需要的所有的信息。

指令参数(准确的说node就是指令的参数)可以通过调用node.jjtGetChild(i)获得,i是参数的序号(序号从0开始)。你可以调用node.jjtGetNumChildren().获得所有的参数。

jjtGetChild(i) 返回的是一个Node 类型的对象,通过这个对象你可以调用node.jjtGetChild(i).value() 方法来获得已经渲染后的变量的值;你也可以通过调用node.jjtGetChild(i).literal()方法来获得字面值。可以通过下面的代码来解释变量的值和字面值的区别:

(String)node.jjtGetChild(0).value() 的结果是 "This is Test Value", 但(String)node.jjtGetChild(0).literal() 的结果是 "This is $test". 当然大多数情况下,你都是需要变量渲染后的值。

要想测试我们新编写的指令,就得先在配置文件中配置如下的内容:

这样就可以运行模板了:

结果是:

编写块指令

现在我们把刚才编写的行内指令改成块指令,就像这样:

上面的代码和前面行内指令的效果一样,参数也是可选的,来看看指令的代码:

有一点不同是:我们额外的实现了Directive 的init() 方法,该方法可以让我们通过访问RuntimeServices 对象来获取logger实例和配置文件中的指令配置。所以我们的指令配置文件可能是这样的:

在配置文件中设置默认值是可选择的,你可以不设置,因为如果在模板中使用指令时没有传可选参数,我们仍然可以在代码中硬编码默认值。比如下面的代码,设置maxLength 的值为配置文件userdirective.truncateBlock.maxLength 的值,如果没有配置文件没有这个配置,则设置默认值为10。

尽管没有配置参数命名的官方推荐,但是使用userdirective.{directive}.{param} 方式可能会比较好。

render() 方法好像有点不太一样了,原因是块指令中的内容在指令节点的最后一个孩子节点上,但是因为所有参数都是可选的,我们需要某种方式来区分指令的参数和指令块的内容。一个办法是检查孩子节点的类型,如果是指令块的内容,则节点类型为ASTBlock,该类型正好与指令的参数不同。为了渲染ASTBlock 节点的内容,我们需要使用

总结

本文讲解了如何编写Apache Velocity自定义的指令,但是在编写自定义指令前,最好先看看考虑一下往模板中设置工具类的方式,因为编写自己的指令需要预留扩展核心模板语言和支持多参数的能力。例如:如果velocity的展示工具集中没有“truncate”方法,一个合适的方法是创建一个新的工具,并把它添加到velocity工具集中,而不是实现一个指令。(即实现指令的成本比较高)

自定义指令不太复杂,但是没有什么文档和教程支持。如果你需要更多的例子,可以参考velocity tool的源码。你也可以check out我的开源项目htmlcompressor,里面有一些块指令。

Reference

Creating Custom Directives for Apache Velocity

http://www.ibm.com/developerworks/cn/java/j-lo-velocity/

优秀的IT技术网站收集

技术开发相关的网站很多,信息多得看不过来,很多优秀的技术网站和博客很难出现在搜索引擎的第一二页,而且很多好的原创文章都是英文的,干货实在是太多了,下面列举了一下:

1. jquery插件库

http://www.jq22.com/

2. spring框架官方文档

http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/

3. 一个不错的java文章网站

http://www.jayway.com/category/java/page/2/

4. java相关的比较有深度的网站

http://www.importnew.com/

5. 开发者头条,开发者的重要资讯

http://toutiao.io/

6. 慕课网,免费学习各种IT技能

http://www.imooc.com/course/list

7. 在线css编辑网站

http://enjoycss.com/

8. java代码搜索网站

http://grepcode.com/

9. jar包搜索网站

http://www.findjar.com/

10. 各编程语言性能对比网站

http://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=java&lang2=go

11. 开源中国的在线工具集和API文档集合,太全面了

http://www.ostools.net/

12. 一个很牛叉的java技术网站(英文的)

http://www.theserverside.com/?asrc=TAB_TheServerSideCOM

13. 一个很不错的java技术网站(英文的)

http://examples.javacodegeeks.com/

14. 一个技术干货很多的额博客

http://ifeve.com/

15. 最新最前沿的java技术咨询

http://www.javaworld.com/

16. 这里汇集了100个的牛x的java技术博客(英文的)

100 High-Quality Java Developers’ Blogs

17. 查看html标签、js对象方法、css属性、SVG等浏览器兼容性的网站,可快速答疑解惑

http://caniuse.com/

这里只列举了我收藏的一些网站,欢迎添加其他不错的网站。

【译】JVM的默认参数

如果在META-INF/MANIFEST.MF文件里面设置了Main-Class属性,那么你就可以很方便的通过java -classpath myapp.jar Main命令甚至更简洁的java -jar myapp.jar命令,来启动java虚拟机。这些命令会使用默认的设置来启动JVM。

要想看到JVM执行时使用在使用哪些参数以及其各参数默认值,可以使用这个命令:

java -XX:+PrintFlagsFinal -version

如果这些参数还不够全,你可以打开几个开关:

java -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+PrintFlagsFinal -version

大部分的参数都可以安全的看到默认值,但是某些参数需要在生产环境运行时来调整。

(后面花括号里面有product的表示可以在启动参数中设置的,花括号里面是“product rw”的表示你还可以在jvm启动后使用类似下面的命令来设置这些参数)

jinfo -flag -PrintHeapAtGC=true <pid>

基本的内存参数设置

对java应用而言,最基本的设置莫过于java堆大小的初始值和最大值设置了(一般认为生产环境初始值和最大值设置成一样的比较合适)。

  • -Xms<size> 设置java堆大小的初始值
  • -Xmx<size> 设置java堆大小的最大值

如果你使用的java7或者更早版本的jdk,你可能发现“OutOfMemoryError: PermGen space”,这时你需要使用-XX:MaxPermSize=<size>参数来增大Perm区大小。server虚拟机默认的Perm区大小是64M,但很多应用所需的Perm区空间不止64M。

如果JVM虚拟机耗尽了堆空间,且GC垃圾回收不了时,会抛出“java.lang.OutOfMemoryError:Java heap space”,要分析内存占用情况,可以添加参数-XX:+HeapDumpOnOutOfMemoryError参数,这个参数会在抛出java.lang.OutOfMemoryError异常时,dump堆空间的数据到当前工作目录下的一个文件中,-XX:HeapDumpPath=<path>可以指定这个dump文件的具体位置

垃圾回收(GC)

垃圾回收策略有很多,这里不细说,可以参考Java Garbage Collection Distilled

不管你正在使用那一个垃圾收集器,你都可以用日志的方式记录一段事件的垃圾回收的过程,来观察垃圾回收的效果。你可以添加参数-verbose:gc来把垃圾回收日志输出到标准输出(STDOUT),但更好的方式是通过参数-Xloggc:<pathtofile>把垃圾回收日志输出到指定的文件。

同时添加-XX:+PrintGCDateStamps参数可以记录下垃圾回收的时间戳,添加-XX:+PrintGCDetails可以记录下垃圾回收的事件细节。

默认情况下,垃圾回收日志文件不断变大,但JVM还内置支持回转日志文件(rotating the file),可搭配使用这三个参数:

  • -XX:+UseGCLogFileRotation 开启回转日志文件
  • -XX:GCLogFileSize=8K 设置单个文件最大的文件大小
  • -XX:NumberOfGCLogFiles=1 设置回转日志文件的个数

最后,你可能需要使用-XX:+DisableExplicitGC参数来显性的关闭手动GC功能(即调用jdk的建议GC的api:System.gc()),让System.gc()方法变成一个空方法,让垃圾回收托管给JVM的垃圾收集器。

实例

java -Xms10g -Xmx10g -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/var/log/myapp/gc.log -XX:+UseGCLogFileRotation -XX:GCLogFileSize=10M -XX:NumberOfGCLogFiles=20 -jar myapp.jar

这个例子使虚拟机分配10GB内存和256MB给perm区(java7)。内存溢出时dump堆空间的共鞥启动了,使用G1(garbage first)垃圾收集器,垃圾收集日志记录到/var/log/myapp/gc.log中,同时开启了日志文件回转功能。

Referece

[译]生产环境中异常堆栈丢失的解决方案

今天我发现生产环境日志中有许多NullPointerException 没有堆栈。我们发现,当一些异常抛出的足够多时,JIT编译器会优化掉异常堆栈。下面的代码可以重现异常堆栈丢失的问题。我们的代码跑在jdk 1.6上(小版本号不记得了)

用下面的命令运行上面代码,异常丢失的情况就出现了(异常栈的长度从2变为了0):

javac NpeThief.java && java -classpath . NpeThief
javac NpeThief.java && java -server -classpath . NpeThief

如何解决呢?下面的参数设置可以修复这个问题,让NullPointerException异常堆栈的长度保持为2,不变为0:
javac NpeThief.java && java -XX:-OmitStackTraceInFastThrow -server -classpath . NpeThief
javac NpeThief.java && java -XX:-OmitStackTraceInFastThrow -classpath . NpeThief
javac NpeThief.java && java -Xint -classpath . NpeThief
javac NpeThief.java && java -Xint -server -classpath . NpeThief

可以看出,解决方案是:使用-XX:-OmitStackTraceInFastThrow参数让JIT不使用对异常堆栈的优化[1,2];或者设置-Xint让虚拟机以解释方式执行类的字节码,不将字节码编译为本机码[3]。那么-XX:-OmitStackTraceInFastThrow参数具体是什么意思呢?

JVM参数-XX:-OmitStackTraceInFastThrow参数可以关掉JVM对堆栈信息的优化。如果设置了这个参数,那么异常堆栈就能完整输出了。

“在服务器中的VM编译器现在提供准确的所有的“冷”内置异常堆栈回溯功能。为了性能考虑,当这些异常被抛出很多次时,这个方法会被重新编译,此后编译器将使用一种更快的抛出异常的方式,即抛出预先分配好的不带堆栈信息的异常。要完全关闭掉这种预分配的异常,就需要使用-XX:-OmitStackTraceInFastThrow参数。“http://java.sun.com/j2se/1.5.0/relnotes.html

请问设置这个参数后会影响性能吗?我确实使用这个方式java [args] -classpath . NpeThief做个了非常天真的测试。如我所期望的,只是编译慢了。但这解决了生产环境的问题吗?

答案是没有,我们需要修改生产环境JVM的启动参数,否则看到的log还是没有堆栈信息的。在我们修改了JVM启动参数,重新部署部署了以后,完整的异常堆栈信息出来了,问题解决了。

Reference

[1] Related sun bug 4292742 NullPointerException with no stack trace
[2] Helpful StackOverflow discussion of NullPointerException missing stack traces
[3] -Xint: Operate in interpreted-only mode. Compilation to native code is disabled, and all bytecodes are executed by the interpreter. The performance benefits offered by the Java HotSpot VMs’ adaptive compiler will not be present in this mode.

本文翻译自:Hotspot caused exceptions to lose their stack traces in production – and the fix

ExceptionInInitializerError和NoClassDefFoundError和ClassNotFoundException的区别

ExceptionInInitializerError和NoClassDefFoundError

以前碰到几次NoClassDefFoundError异常,网上都说是因为静态语句块出现异常导致,但是我总是无法通过自己编写得简单事例代码重现(我自己写的代码总是抛出ExceptionInInitializerError,而不是NoClassDefFoundError),直到最近从其他文章看到了一段重现代码才了解了。先看下面可以抛出NoClassDefFoundError异常的代码:

可以看到,Bar和Foot两个写到一个文件Foo.java里面的,所以肯定都在classpath里面,这两个类一定是能找到的。这个代码Bar类的静态属性BAR_ID在类初始化的时候会抛出ArithmeticException异常,导致类初始化失败(不同于对象初始化,即new一个对象,类初始化先于对象初始化)。在第一使用到Bar类时,就会加载并初始化类Bar,抛出的是ExceptionInInitializerError异常导致类加载失败了,当第二次使用一个类加载和初始化失败了的类Bar时,就会抛出NoClassDefFoundError异常了,从上面的代码可以看到除了第一次外,其他几次使用到Bar类时都抛出了NoClassDefFoundError。

其中有意思的是,bar对象的创建失败,导致bar==null,但是仍然可以调用bar的静态方法。

上面的例子是静态属性,下面看一个静态语句块的例子:

其实效果上,静态语句块和静态属性的初始化是一样的。Bar2类第一次加载并初始化失败,就会抛出ExceptionInInitializerError异常。其他时候使用到Bar2类时会抛出NoClassDefFoundError。

ClassNotFoundException

先看段代码:

类ClassNotExist根本不在classpath中,那么就会抛出ClassNotFoundException

总结

尽管jdk里面有注释,但是那也比较难理解,下面整理了下,这三种异常的主要区别

  • ClassNotFoundException:类不在classpath下
  • ExceptionInInitializerError:类在classpath下,类在第一次加载和初始化时静态属性初始化或者静态语句中抛出其他异常,会导致抛出ExceptionInInitializerError异常
  • NoClassDefFoundError:类在classpath下,在类已经加载和初始化过一次,但那次加载和初始化失败了,现在又要使用这个类时就会抛出NoClassDefFoundError异常,其本质原因和ExceptionInInitializerError异常一样,都是因为静态属性初始化或者静态语句块抛出异常导致。

Reference

Why am I getting a NoClassDefFoundError in Java?

 

java.lang.UnsupportedOperationException at javax.crypto.CipherSpi.engineGetKeySize

Unlimited Strength Java(TM) Cryptography Extension (JCE)
Unlimited Strength Java(TM) Cryptography
Extension (JCE)

java.lang.UnsupportedOperationException at javax.crypto.CipherSpi.engineGetKeySize的原因是默认的jdk的key长度有128bit限制,所以需要把这个长度限制放宽。官方有个无长度限制版的JCE jar包,下载地址:

jdk6:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html

jdk7:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html

解压后,将两个jar包(local_policy.jar、US_export_policy.jar)放在如下的目录下:

<java-home>/lib/security              [Unix]
<java-home>/jre/lib/security        [Unix]

<java-home>\lib\security              [Win32]
<java-home>\jre\lib\security        [Win32]

Reference

http://docs.oracle.com/javase/1.5.0/docs/guide/security/jce/JCERefGuide.html#Introduction

local_policy.jar and US_export_policy.jar different with Unlimited Strength Vs Default.

 

java.lang.NoClassDefFoundError: com/sun/mail/util/SharedByteArrayInputStream

java classloader体系结构
java classloader体系结构

最近碰到jar包冲突,抛出的异常:java.lang.NoClassDefFoundError: com/sun/mail/util/SharedByteArrayInputStream,导致发邮件发布出去。在线下无法重现,而线上确大部分情况都能重现。

在http://www.findjar.com搜了一下发现含有这个类的有mail-1.3.3.jar,而我工程里面使用的mail-1.4.5.jar。这样一来似乎把mail包的版本降低到1.3.3就行了。

but倔强的我不想降版本,所以不得不找出到底是哪里使用到了com.sum.mail.util.SharedByteArrayInputStream类,在网上百度了一番,发现j2ee.jar有嫌疑,我用的是j2ee-1.4.jar和mail-1.4.5.jar,把这个jar包使用jd反编译后看到了原因,j2ee-1.4.jar和mail-1.4.5.jar中都使用了javax.mail.internet.MimeMessage类,而j2ee-1.4.jar中的MimeMessage中import了com.sun.mail.util.SharedByteArrayInputStream类,mail-1.4.5.jar中的MimeMessage中import了javax.mail.util.SharedByteArrayInputStream类。在线下很可能mail-1.4.5.jar包中的MimeMessage类被优先load到jvm中,所以使用了javax.mail.util.SharedByteArrayInputStream类;而线上则很可能j2ee-1.4.jar中的MimeMessage类被优先load到jvm中,而com.sun.mail.util.SharedByteArrayInputStream类在整个web工程中确实不存在,所以出现了如题的异常。

这样一来解决的办法就是得升级j2ee.jar包,经查j2ee-5.jar使用MimeMessage类与mail-1.4.5.jar中的完全一样,都import的是javax.mail.util.SharedByteArrayInputStream类。这样把升级j2ee包到版本5就行了。

总结一下:j2ee-1.4.jar与mail-1.3.3jar匹配,j2ee-5.jar与mail-1.4.5.jar匹配即可避免jar包冲突。

error the @annotation pointcut expression is only supported at Java 5 compliance level or above

碰到了这个error the @annotation pointcut expression is only supported at Java 5 compliance level or above报错,一看就知道是jar包冲突,在网上百度了半天,大部分的说法都是把org.aspectj:aspectjweaver:jar包升级到1.6以上,但是我尝试了还是不行。这种spring的jar冲突,基本只能考百度谷歌了。

后来把dependency tree打印出来,搜索aspectj后,发现我的工程里面除了aspectjweaver外,还有个aspectjtools,这两个的版本都偏低,于是把这两个包都升级到了1.7.3,重新mvn clean install eclipse:eclipse后果然成功。

我只想说这些jar包冲突真的好坑爹啊

Reference

Error when using AspectJ AOP with Java 7