最后还要谈谈代价问题。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吧。 |