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

今天我发现生产环境日志中有许多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?

 

linux管理多个ssh公钥密钥

很多网上免ssh密码使用git的命令都没有考虑到多个公钥密钥的情况,这里搜集并整理一下管理多个ssh公钥密钥的步骤。

首先生成公钥和密钥,默认情况公钥的文件名比密钥末尾多.pub,下面以管理coding.net和github.com两个git仓库的ssh公钥密钥为例(如果是在windows下,请安装git客户端然后使用git bash来键入下面的命令;如果是在linux下,则直接使用终端即可):

这个命令直接通过-f参数指定了密钥和公钥的文件名,比如-f ~/.ssh/id_rsa.coding,其生产的密钥为~/.ssh/id_rsa.coding,公钥为~/.ssh/id_rsa.coding.pub,生成好了公钥和密钥后,把公钥文件中的内容追加到~/.ssh/authorized_keys文件的末尾,同理github的公钥密钥的生成方式类似。注意追加前authorized_keys文件的末尾需要有个换行符。追加后的效果可能是这样的:

同时把这个公钥添加到git仓库ssh key管理的地方,比如github是在Settings–>SSH Keys里面

当有多个ssh密钥需要管理时,一定要修改config文件的权限,否则你配置后仍然时无效的

~/.ssh/config文件的内容为:

其中User后面的值为你访问的git ssh地址的@之前的部分,比如:git@github.com:dongritengfei/beego.git的@前面是git,所以User后面的值为git。Host就是你的git仓库的域名或者IP。

然后是用这个命令来测试是否配置ok

如果你看到这个就说明你ok了,如果你看到下面的样子:

那你需要执行:

然后再试试应该就可以了。

其他

上面的说的SSH的情况,如果你使用的HTTPS的方式clone代码的话,需要免登就需要这样:http://www.akmumu.com/2015/06/02/360.html

input不能输入空格解决方法

最近发现页面中一个有suggest功能的input不能输入空格(这个input的suggest功能用到了bootstrap的dropdown功能),但是可以输入其他的字符,非常奇怪,我自己的js代码里面根本没有对输入做过任何的处理,而且输入空格时文本框中的内容没有任何变化。

只能猜测可能是其他的js中对所有的dropdown功能的input都做了按键监听和屏蔽。

无耐之下,用排除法,一个个的把页面中引入的js移除,查看输入框能否输入空格,经测试发现当删除了bootstrap的js引入后,input就可以输入空格了,说明问题在bootstrap.js里面,所以进到源码里面看了看,发现版本是v3.3.0,因此试试最新的版本v3.3.2,替换最新的版本后,恢复正常了。然后用svn的diff功能看了下两个版本源码中的区别,发现v3.3.2中修复了v3.3.0中的这个bug,增加了一个判断条件把文本框类型的控件过滤掉了。如下图:

bootstrap的bug:input不能输入空格
bootstrap的bug:input不能输入空格

参考ASCII表,空格的ascii码是32,还有三个按键也被屏蔽了,分别是:&、(、escape键。v3.3.0中的代码可以看到,如果是空格、&、(、escape键这4个按键中的任何一个,则执行:

e.preventDefault()
e.stopPropagation()

也就是说如果按的是上面的4个按键中的任何一个,就阻止这个事件的默认行为,所以文本框中的内容没有变化。v3.3.2版本中增加了一个控件tagName的判断,如果是input或者textarea就不做上面的阻止行为。这样正好解决了问题。

结论是:bootstrap v3.3.0里面的bug,bootstrap升级到最新的3.3.2就可以了。