你应该远离的6个Java特性

你应该远离的6个Java特性
作者 张龙 发布于 十一月 12, 2013 | 5 讨论

新浪微博 腾讯微博 豆瓣网 Twitter Facebook linkedin 邮件分享 更多 9
“稍后阅读”
“我的阅读清单”

Nikita Salnikov Tarnovski是plumbr的高级开发者,也是一位应用性能调优的专家,他拥有多年的性能调优经验。近日,Tarnovski撰文谈到了普通开发者应该尽量避免使用的6个Java特性,这些特性常见于各种框架或库当中,但对于普通的应用开发者来说,使用这些特性也许会给你所开发的应用带来灾难。

我曾花费了无数个小时为各种不同的应用排错。根据过往的经验我可以得出这样一个结论,那就是对于大多数开发者来说,你应该远离几个Java SE特性或是APIs。这里所说的大多数开发者指的是一般的Java EE开发者而不是库设计者或是基础设施开发者。

坦白地说,从长远来看,大多数团队都应该远离如下的Java特性。不过凡事总有例外的情况。如果你有一个强大的团队,总是能够清楚地意识到自己在做什么,那就按照你的想法去做就行。但对于大多数情况来说,如果你在项目的开发中使用了下面这几个Java特性,那么从长远来看你是会后悔的。

这些应该远离的Java特性有:

反射
字节码操纵
ThreadLocal
类加载器
弱引用与软引用
Sockets

下面对这些特性进行逐个分析,看看为什么普通的Java开发者应该远离他们:

反射:在流行的库如Spring和Hibernate中,反射自然有其用武之地。不过内省业务代码在很多时候都不是一件好事,原因有很多,一般情况下我总是建议大家不要使用反射。

首先是代码可读性与工具支持。打开熟悉的IDE,寻找你的Java代码的内部依赖,很容易吧。现在,使用反射来替换掉你的代码然后再试一下,结果如何呢?如果通过反射来修改已经封装好的对象状态,那么结果将会变得更加不可控。请看看如下示例代码:

如果这样做就无法得到编译期的安全保证。就像上面这个示例一样,你会发现如果getDeclaredField()方法调用的参数输错了,那么只有在运行期才能发现。要知道的是,寻找运行期Bug的难度要远远超过编译期的Bug。

最后还要谈谈代价问题。JIT对反射的优化程度是不同的,有些优化时间会更长一些,而有些甚至是无法应用优化。因此,有时反射的性能损失可以达到几个数量级的差别。不过在典型的业务应用中,你可能不会注意到这个代价。

总结一下,我觉得在业务代码中唯一合理(直接)使用反射的场景是通过AOP。除此之外,你最好远离反射这一特性。

字节码操纵:如果在Java EE应用代码中直接使用了CGLIB或是ASM库,那么我建议你好好审视一下。就像方才我提到的反射带来的消极影响,使用字节码操纵所带来的痛苦可能是反射的好几倍之多。

更糟糕的是在编译期你根本就看不到可执行的代码。从本质上来说,你不知道产品中实际运行的是什么代码。因此在面对运行期的问题以及调试时,你要花费更多的时间。

ThreadLocal:看到业务代码中如果出现ThreadLocal会让我感到颤抖,原因有二。首先,借助于ThreadLocal,你可以不必显式通过方法调用就可以传递变量,而且会对这种做法上瘾。在某些情况下这么做可能是合理的,不过如果不小心,那么我可以保证最后代码中会出现大量意想不到的依赖。

第二个原因与我每天的工作有关。将数据存储在ThreadLocal中很容易造成内存泄漏,至少我所看到的十个永久代泄漏中就有一个是由过量使用ThreadLocal导致的。连同类加载器及线程池的使用,“java.lang.OutOfMemoryError:Permgen space”就在不远处等着你呢。

类加载器:首先,类加载器是个很复杂的东西。你必须首先理解他们,包括层次关系、委托机制以及类缓存等等。即便你觉得自己已经精通了类加载器,一开始使用时还是会出现各种各样的问题,很可能会导致类加载器泄漏问题。因此,我建议大家还是将类加载器留给应用服务器使用吧。

弱引用与软引用:关于弱引用与软引用,你是不是只知道他们是什么以及简单的使用方式而已?现在的你对Java内核有了更好的理解,那会不会使用软引用重写所有的缓存呢?这么做可不太好,可不能手里有锤子就到处找鼓敲吧。

你可能很想知道我为什么说缓存不太适用使用软引用吧。毕竟,使用软引用来构建缓存可以很好地说明将某些复杂性委托给GC来完成而不是自己去实现这一准则。

下面来举个例子吧。你使用软引用构建了一个缓存,这样当内存行将耗尽时,GC会介入并开始清理。但现在你根本就无法控制哪些对象会从缓存中删除,很有可能在下一次缓存中不再有这个对象时重新创建一次。如果内存还是很紧张,又触发GC执行了一次清理,那么很有可能会出现一个死循环,应用会占用大量CPU时间,Full GC也会不断执行。

Sockets:java.net.Socket简直太难使用了。我认为它的缺陷归根结底源自其阻塞的本质。在编写具有Web前端的典型的Java EE应用时,你需要高度的并发性来支持大量的用户访问。这时你最不想发生的事情就是让可伸缩性不那么好的线程池呆在那儿,等待着阻塞的Sockets。

现在已经出现了非常棒的第三方库来解决这些问题,别自己写了,尝试一下Netty吧。

各位InfoQ读者,Java出现至今经历了多次版本更迭,每次也都会有诸多新特性的加入。在日常的Java开发中,你认为存在哪些Java特性是很容易导致问题的呢?作者提到不建议在普通的应用开发中使用反射,不过对于一些框架或库的开发,离开反射实际上是无法实现的,例如Spring、Struts2等框架,那么在一般的Java项目开发中,你觉得哪些地方有使用反射的必要呢?换句话说,如果不使用反射就实现不了功能或是需求。文中作者也不建议使用字节码操纵,实际上一些框架在实现某些功能时是必须要使用的,比如说Spring在实现AOP时就使用了Java的动态代理与CGLib库两种方式来达成的。那么对于一般的Java项目来说,哪些地方需要用到字节码操纵呢?欢迎各位读者畅所欲言,一起讨论这些有趣的话题。

This entry was posted in Best Practices. Bookmark the permalink.

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s