eclipse的hot swap功能失效导致代码修改后必须重启web应用:Absent Line Number Information

很长时间一直被ecipse的hotSwap功能失效问题困扰,hotSwap是jvm的一个重要功能,它可以让你在正在运行的程序中,修改方法体内容,修改后的代码能在应用不重启的条件下生效。而且经常提示这样的错误:

Absent Line Number Information

The virtual machine was unable to remove all stack frames running old code from the call stack. The virtual machine is not supplying the debugger with valid data for those frames. Stepping into these obsolete frames may be hazardous to the target virtual machine

hotSwap这个功能能大大降低开发调试成本。可是原本eclipse中能正常工作的hotSwap功能居然不能用了。在网上找了很多的方案,基本都是在说Preferences –> Java –> Compiler –> Classfile Generation的几项要勾上。但是eclipse默认这几项就是勾上的。

经查阅n多资料后,发现其本质原因是虚拟机和调试器不配套。说明很可能有两套javac.exe(用来编译的)和jdb.exe(用来调试的)同时被使用了,而这两套还不是来自同一个jdk的。顺着这个思路,我查看了下我的环境变量,在cmd中敲”echo %PATH%”:

C:\Program Files (x86)\Common Files\NetSarang;D:\amd\AMD APP SDK\2.9\bin\x86_64;D:\amd\AMD APP SDK\2.9\bin\x86;C:\Program Files (x86)\AMD APP SDK\2.9\bin\x86_64;C:\Program Files (x86)\AMD APP SDK\2.9\bin\x86;%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;D:\software\apache-maven-3.0.5\bin;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;%GOBIN%;C:\Program Files (x86)\Git\cmd;%GROOVY_HOME%\bin;C:\Program Files\TortoiseSVN\bin

jdk只配置了一个,看起来没什么异样。我尝试用everything软件在整个硬盘上搜索javac.exe,结果发现有两个javac.exe,一个在jdk下,还有一个在%SystemRoot%\system32下,而且这个system32目录下还有个java.exe。看来问题应该是在这里了,至于为什么system32下会有javac.exe,就不得而知了,把java相关的exe文件都删除后,再试了试果然hotSwap功能恢复了。

困扰我许久的问题终于被解开了。

Reference

http://stackoverflow.com/questions/957822/eclipse-unable-to-install-breakpoint-due-to-missing-line-number-attributes

java dns解析缓存之源码解析

java默认情况会缓存dns解析的结果,导致的结果是当域名对应的ip已经变化后,正在运行的java程序不会立刻知道ip的变化,而是仍然访问的变化前的ip。问题来了,默认情况java虚拟机会缓存dns解析结果多少秒?如何修改缓存时间呢?能不能把dns解析缓存关掉呢?就这些问题,我研究了下源码,以下是源码解析。

dns缓存机制

以获取一个域名www.1688.com对应的ip为例,其代码踪迹是这样的:

  1. InetAddress.getByName(“www.1688.com”)
  2. InetAddress.getAllByName(“www.1688.com”)[0]
  3. InetAddress.getAllByName(“www.1688.com”, null)
  4. InetAddress.getAllByName0(“www.1688.com”, null, true)
  5. InetAddress.getCachedAddress(“www.1688.com”)

InetAddress.getCachedAddress的代码如下:

可以看到cache有两个,即有效DNS解析的cache(addressCache)和无效DNS解析的cache(negativeCache),为了让dns解析效率更高java很聪明的把dns解析正确的域名保存下来了,方便后续再查时能最快的返回结果;同时也缓存了无效的域名,因为根据DNS解析的原理,往往无效的域名解析耗时比正常的域名解析时间要长,所以缓存无效的域名可以有效的避免浪费时间在查找无效域名上,提升代码性能。下面是java.net.InetAddress$Cache的源码:

从上面的Cache类的get方法可以看到:

  • 如果policy为0,那么cache的get方法就永远返回null,相当于cache不起作用了;
  • 如果policy为-1,那么cache缓存过的entry永远都不会过期,也就是说就算域名对应的ip变了,cache再也不更新ip了
  • 如果policy为n,根据“System.currentTimeMillis() + (policy * 1000)”,那么这个entry就可以在cache中存活n秒钟,即n秒后,该entry会被移除出cache

问题是policy怎么来的,看方法Cache.getPolicy()方法里面,主要是InetAddressCachePolicy类的get()和getNegative()方法。看sun.net.InetAddressCachePolicy类的源码:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/sun/net/InetAddressCachePolicy.java#InetAddressCachePolicy

 

看到这个代码就恍然大悟了,policy的值可以来自两个地方(值根据需要自己设置,此处为示例):

  • 一个是在java.security文件中配置networkaddress.cache.ttl=1111networkaddress.cache.negative.ttl=2222
  • 另一个是在启动单数中添加这两个参数-Dsun.net.inetaddr.ttl=1111-Dsun.net.inetaddr.negative.ttl=2222

而且这两中配置方法有一个优先级,即第一种方法优先级高,但是第一种方法需要在打开Java中的SecurityManager,若没有打开则${java.home}/jre/lib/security/java.security文件的配置将不会生效,可以参考http://www.314p.com/gywm.aspx?lm=30&wzid=4077打开Java中的SecurityManager。

下面附上我本机的jdk1.6.0_35的${java.home}/jre/lib/security/java.security文件的部分源码:

以上就是默认的配置,可以看到

  • #networkaddress.cache.ttl=-1这一行是被注释掉的,也就是说默认的DEFAULT_POSITIVE,即30秒更新一次可访问的DNS解析
  • networkaddress.cache.negative.ttl=10没有被注释掉,说明不可访问的DNS解析缓存10秒

DNS解析缓存实现的一点思考

看了源码之后才发现里面有多处synchronized锁,主要是两块:

但是若仔细看会发现,对cache的synchronized是在getCachedAddress(host)方法上,也就是说每个获取域名的方法都需要调这个方法,而这个方法里面有对cache的synchronized,这会不会是一个性能考虑的点呢?这里是读cache,并不是写cache,为了保证写的时候(可能cache会扩容),度不会出问题,加了synchronized,感觉不值啊,这也是不建议有事没事调用InetAddress.getByName(host)的原因。

个人觉得,是否可以考虑把这个地方设计成Copy-On-Write的Map,也就是说在写cache的时候,copy一份写,读不加锁,写完后,把cache替换一下,可以保证数据的最终一致性。这样写性能还行,读没有任何锁,性能很多,对于获取域名这种场景,肯定是读远多于写的。

这里一篇关于Copy-On-Write的文章http://www.kankanews.com/ICkengine/archives/118889.shtml可以参考

看看大家的意见,欢迎抛砖。

 

 

 

eclipse中文乱码问题解决方案汇总

eclipse中文乱码都是因为字符编码与默认的编码不符合导致的,有很多的方法可以解决,不需要安装任何插件就可以搞定。针对不同的情况,需要使用不同的方案,下面就针对一些案例讲解如何解决乱码问题。解决乱码问题的主要思路是设置正确合适的编码,如果不知道目标文件原本的编码,可以进行一定的尝试,通常尝试下GBK和UTF-8这两个编码即可。

1. 设置单个文件的字符编码,解决单个文件的乱码问题

有时候不小心copy来的单个文件编码与你workspace的默认编码不一致,就导致了单个乱码。解决办法:在Pakcage Explorer或者Project Explorer视图里面,右键点击该文件–>选择“Properties”–>”Text file encoding”–>给”Other”项设置相应的编码。(需要注意的是,如果copy来的文件在eclipse中显示的是正常,但是编码与其他文件不一致,若你想统一编码,就需要在设置编码前,记得先把文件内容copy一下,然后设置好编码,再把copy的内容粘贴到编码修改后的文件中,这样会不乱码;一修改编码文件内容就会乱码),如下图:

eclipse设置单个文件的字符编码
eclipse设置单个文件的字符编码

2. 设置第三方jar包的字符编码,解决整个jar的乱码问题

第三方jar包的编码问题可能是最常见的问题,其解决方案与单个文件的比较类似,在Pakcage Explorer或者Project Explorer视图里面,右键第三方jar包–>选择“Properties”–>给”Encoding”项设置相应的编码,如下图:

在eclipse中给jar包设置字符编码
在eclipse中给jar包设置字符编码

3. 分别设置不同类型文件的字符编码

有时候为了统一所有工程的编码,可能需要提前设置好各类型文件的字符编码,比如:把所有的jar类都设置为GBK编码,把所有的xml设置为UTF-8编码等。这时候就需要有针对性的给不同类型的文件设置不同的编码。步骤是这样的:Windows–>Perferences–>General–>Content Types–>选择文件类型–>设置“Default encoding”项。如图下:

在eclipse中给指定类型的文件设置字符编码
在eclipse中给指定类型的文件设置字符编码

4. 设置整个workspace的文本文件的默认字符编码

往往workspace都有一个默认的字符编码,如果你的工程大部分或者全部是另一个编码,那么你可以重新设置一个默认的字符编码,后续新建工程就不需要再去设置编码了。步骤是这样的:Windows–>Perferences–>General–>Workspace–>Test file encoding–>给“Other:”设置相应的编码,如下图:

在eclipse中设置workspace的字符编码
在eclipse中设置workspace的字符编码

个人经验

其实一般开源的代码都是UTF-8编码的,我们平时开发的时候最好也都统一使用UTF-8,在开发初期直接把workspace的文本文件的编码设置为UTF-8,这比较省事。在开发过程中,如果发现添加进来的三方jar包编码不是UTF-8,那么可以针对这个jar设置其编码;若copy某个源码文件到工程中发现是乱码的,那么可以仅设置这一个文件的编码成正确的编码。

自定义eclipse快捷键

大家有木有发现,尽管eclipse的快捷键很多,但是仍然有很多常用操作没有快捷键,比如:跳过所有的断点、删除所有的断点等。但是后来才发现其实eclipse有很多操作可以有快捷键,但是默认没有提供,我们可以通过自定义eclipse快捷键的方式来满足自己的高效coding和debug的需求。

自定义快捷键意味着,可以添加可以添加的快捷键,同时也可以修改快捷键(如果你对某快捷键不爽的话)。

自定义eclipse快捷键的步骤

下面介绍下eclipse快捷键的配置方法,如下图所示:

自定义eclipse快捷键
自定义eclipse快捷键

在Windows–>Perferences–>General–>Keys里面可以设置所有eclipse的快捷键。打开可以看到只是部分的功能有快捷键,还有不少的功能没有快捷键,这些快捷键都可以自定义,比如要设置ctrl+shift+1为跳过所有断点,可以先在命令过滤文本框里面输入想要设置的命令或者操作的英文名称(这个需要意会了,比如调试相关的命令,可以输入“breakpoint”,就能找到,如果实在不知道叫啥,就拖动滚动条一个个的看吧),跳过所有断点肯定是和breakpoint有关,所以输入breakpoint,可以看到列表里面有了所有和断点相关的命令,选择“Skip All Breakpoint”,然后在“Binding”里面按下ctrl+shift+1,再在“When”里面选择“In Windows”即可。

快捷键的上下文(Context)

其中“When”是告诉你,你设置的快捷键的会在什么场景下可用,或者说上下文是什么,In windows表示所有窗口,该快捷键的命令都会执行。这个上下文的还有个继承的潜规则:

  • In Dialogs and Windows
  • In Windows (extends In Dialogs and Windows)
  • In Dialogs (extends In Dialogs and Windows)
  • Editing Text (extends In Windows)
  • Editing Java Source (extends Editing Text)
  • Debugging (extends In Windows)
  • Debugging Java (extends Debugging)

Schema

从上面的图片中可以看到快捷键设置中可以选择schema,默认的schema是Default,其实eclipse自带了emacs版的快捷键,喜欢emacs的同学可以使用这个schema。其中也有个潜规则,就是emacs schema继承了Default schema的快捷键,如果emacs的快捷键在Default中也有,那么emacs的快捷键优先级高,会覆盖Default的快捷键。

个人经验

想必不少人设置快捷键的时候担心会和其他快捷键冲突,所以不知道应该把某命令设置成什么快捷键。其实这个问题还好说,因为“Conflict”里面会告诉你你当前设置的快捷键和那些其他快捷键在什么场景上下文下冲突。经过我自己的测试,我发现一些的9个组合基本没有被eclipse使用到,大家可以放心设置自己的快捷键。即ctrl+shift+1~9,,希望能对大家提供参考。

Reference

eclipse的帮助文档快捷键章节:Help–>Workbench User Guide–>Reference–>Perferences–>Keys

使用iPOJO时抛出异常java.lang.UnsupportedOperationException: Cannot add elements inside this collection

 

felix ipojo
felix ipojo

Felix iPOJO的功能很强大,配置也比较复杂,配置和实现代码之间有着千丝万缕的关联,完全脱离代码去配置iPOJO的component和instance势必不会达到自己想要的依赖注入效果,甚至很难查到问题的原因。尽管iPOJO的配置有iPOJO xml schemaiPOJO Annotation,但是仅仅按照schema和annotation来不一定能配置正确,还需要理解iPOJO配置背后的一些机制和原理,否则会出现一些奇怪的异常。见如下的代码和iPOJO annotation注解:

上面的代码乍一看没啥问题,但是其实这个component的timeoutConfigList属性是无法注入成功的,这和iPOJO的一些的依赖注入实现机制有关系,如果用以上的代码来配置instance,在启动这个component和instance所在的bundle时会抛出以下的异常堆栈:

我们可以看到属性timeoutConfigList是List集合类型,它上有@Requires的annotaion,又有对这个属性的@Bind和@Unbind方法。这种用法是不合适的。如果使用不当就会出现上面的异常。因为当在一个属性上注明了@Requires的annotaion时,就表示这个属性需要由ipojo来负责依赖注入,ipojo默认会为集合类型的属性创建一个ServiceCollection类的代理对象,也就是说timeoutConfigList属性在被替换成了ServiceCollection类的对象,调用这个对象里的所有修改方法都会抛出UnsupportedOperationException异常,因为这个属性已经交由ipojo的来维护了,对其进行修改、添加或者删除的话,可能会导致ipojo维护对象的混乱。而@Bind和@Unbind方法是在属性的代理对象创建后,当有适合的属性类型的对象满足注入条件时回调的方法,在以上的代码中有add和remove的操作自然会导致抛出上面的异常了。

解决办法

方法一: 同时使用@Requires注解以及@Bind和@Unbind方法时,保证@Bind和@Unbind方法中没有修改操作(可以执行非修改操作,比如contains、size、get等,详见ServiceCollection类的源码)

方法二: @Requires注解与@Bind、@Unbind方法不同时出现。其实@Bind、@Unbind方法基本包含了@Requires注解的功能,而且还可以对这个属性对象的注入进行aop操作,相比@Requires注解只是需要多写些判空和初始化的代码而已。

方法三:在示例代码的基础上,在@Requires的注解中添加proxy=false,这样示例代码就不会抛出异常了(这个时候这个属性在没有被bind过前,其值为null,所以需要做判空处理),比如下面的写法:

(如果没有使用annotation,而是component的配置也是用的xml方式,解决方案还是如上面的讲的三点相类似。)