BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

你应该远离的6个Java特性

| 作者 张龙 关注 14 他的粉丝 发布于 2013年11月13日. 估计阅读时间: 7 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

Nikita Salnikov Tarnovskiplumbr的高级开发者,也是一位应用性能调优的专家,他拥有多年的性能调优经验。近日,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项目来说,哪些地方需要用到字节码操纵呢?欢迎各位读者畅所欲言,一起讨论这些有趣的话题。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

ThreadLocal在不得不用的时候采用 by 曹 云飞

在两种情况下回使用ThreadLocal:
并发情况需要需要每个线程访问变量的一个副本;
在同一个线程内隐蔽的传递变量。

善意的提醒,说远离有点夸张 by Zhang Hui

哎。。。搞程序的,也喜欢来搞标题党。算是对程序员使用相关特性的一种善意的提醒,而不是远离。比如反射和Threadlocal,如果不用这两个东西很多功能是做不出来。

反射,应尽可能封装在组件里 by zhou flybean

话说怎么没有static?内存泄露十次有九次与它有关,哈哈

远离?你准备写一辈子的hello world? by 严 海东

远离?你准备写一辈子的hello world?

曾经被坑过 by Liu Yi

我就说一个事儿, 当然我的本意不是所谓的远离什么。。。。。
曾经用过某知名外企的一个产品, 它在TheadLocal中存储了login user的东西(比如某些场景需要临时用管理员角色去操作, 而不是当前登录用户), 如果一个thread被用来服务请求, 那个产品首先检查这个thread中是否有user的信息, 如果有, 它就会覆盖真正的登陆用户(JAAS), 当然它的api是说明了每次最好popup一下, 但是我们那个项目是好几个team合作开发的, 由于时间比较紧, 这块最开始也没写什么template来供大家统一调用,最后整的大家每次在自己的代码中开始都要把这个login user清空。。。。。。

远离java,拥抱scala by zhang zhijia,.

最好的方式是不要用java,使用scala

Re: 远离java,拥抱scala by n yuxiao

bingo

没错 by 傅 强

写得好

Re: 曾经被坑过 by Zhefu Zhou

呵呵,这个纯粹是前期设计没有做好,迭代设计也没有人负责的结果,是不是ThreadLocal却是不重要了。

Re: 远离java,拥抱scala by Zheng Ken

我觉得groovy比scala好用多了

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

10 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT