版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Java程序性能优化实战目录TOC\h\h第1章Java性能调优概述\h1.1性能概述\h1.1.1看懂程序的性能\h1.1.2性能的参考指标\h1.1.3木桶原理与性能瓶颈\h1.1.4Amdahl定律\h1.2性能调优的层次\h1.2.1设计调优\h1.2.2代码调优\h1.2.3JVM调优\h1.2.4数据库调优\h1.2.5操作系统调优\h1.3基本调优策略和手段\h1.3.1优化的一般步骤\h1.3.2系统优化的注意事项\h1.4小结\h第2章设计优化\h2.1善用设计模式\h2.1.1单例模式\h2.1.2代理模式\h2.1.3享元模式\h2.1.4装饰者模式\h2.1.5观察者模式\h2.1.6值对象模式\h2.1.7业务代理模式\h2.2常用的优化组件和方法\h2.2.1缓冲\h2.2.2缓存\h2.2.3对象复用——池\h2.2.4并行替代串行\h2.2.5负载均衡\h2.2.6时间换空间\h2.2.7空间换时间\h2.3小结\h第3章Java程序优化\h3.1字符串优化处理\h3.1.1String对象及其特点\h3.1.2substring()方法的内存泄漏\h3.1.3字符串分割和查找\h3.1.4StringBuffer和StringBuilder\h3.1.5CompactStrings优化字符串存储\h3.2核心数据结构\h3.2.1List接口\h3.2.2Map接口\h3.2.3Set接口\h3.2.4优化集合访问代码\h3.2.5RandomAccess接口\h3.3使用NIO提升性能\h3.3.1NIO中的Buffer类族和Channel\h3.3.2Buffer的基本原理\h3.3.3Buffer的相关操作\h3.3.4MappedByteBuffer性能评估\h3.3.5直接访问内存\h3.4引用类型\h3.4.1强引用\h3.4.2软引用\h3.4.3弱引用\h3.4.4虚引用\h3.4.5WeakHashMap类及其实现\h3.5性能测试工具JMH\h3.5.1JMH之HelloWorld\h3.5.2JMH之指定测量模式\h3.5.3JMH之对象作用域\h3.5.4JMH之代码消除\h3.6有助于改善性能的技巧\h3.6.1使用局部变量\h3.6.2位运算代替乘除法\h3.6.3替换switch\h3.6.4一维数组代替二维数组\h3.6.5提取表达式\h3.6.6展开循环\h3.6.7布尔运算代替位运算\h3.6.8使用arrayCopy()\h3.6.9使用Buffer进行I/O操作\h3.6.10使用clone()代替new\h3.6.11慎用Java函数式编程\h3.7小结\h第4章并行程序开发及优化\h4.1并行程序设计模式\h4.1.1Future模式\h4.1.2Master-Worker模式\h4.1.3GuardedSuspension模式\h4.1.4不变模式\h4.1.5生产者-消费者模式\h4.2JDK多任务执行框架\h4.2.1无限制线程的缺陷\h4.2.2简单的线程池实现\h4.2.3Executor框架\h4.2.4自定义线程池\h4.2.5优化线程池大小\h4.2.6扩展ThreadPoolExecutor\h4.3JDK并发数据结构\h4.3.1并发List\h4.3.2并发Set\h4.3.3并发Map\h4.3.4并发Queue\h4.3.5并发Deque\h4.4并发控制方法\h4.4.1Java内存模型与volatile\h4.4.2同步关键字synchronized\h4.4.3重入锁\h4.4.4读写锁\h4.4.5读写锁的改进:StampedLock\h4.4.6Condition对象\h4.4.7信号量\h4.4.8线程局部变量ThreadLocal\h4.5锁的性能和优化\h4.5.1线程的开销\h4.5.2避免死锁\h4.5.3减少锁持有时间\h4.5.4减小锁粒度\h4.5.5读写分离锁来替换独占锁\h4.5.6锁分离\h4.5.7重入锁和内部锁\h4.5.8锁粗化\h4.5.9自旋锁\h4.5.10锁消除\h4.5.11锁偏向\h4.6无锁的并行计算\h4.6.1非阻塞的同步/无锁\h4.6.2原子操作\h4.6.3Amino框架简介\h4.6.4Amino集合\h4.6.5Amino树\h4.6.6Amino图\h4.6.7Amino简单调度模式\h4.7协程\h4.7.1协程的概念\h4.7.2Kilim框架简介\h4.7.3Task及其状态\h4.7.4Fiber及其状态\h4.7.5Kilim开发环境配置\h4.7.6Kilim之HelloWorld\h4.7.7多任务通信\h4.7.8Kilim实例及性能评估\h4.8小结\h第5章JVM调优\h5.1Java虚拟机内存模型\h5.1.1程序计数器\h5.1.2Java虚拟机栈\h5.1.3本地方法栈\h5.1.4Java堆\h5.1.5方法区\h5.2JVM内存分配参数\h5.2.1设置最大堆内存\h5.2.2设置最小堆内存\h5.2.3设置新生代\h5.2.4设置持久代\h5.2.5设置线程栈\h5.2.6堆的比例分配\h5.2.7堆分配参数总结\h5.3垃圾收集基础\h5.3.1垃圾收集的作用\h5.3.2垃圾回收算法与思想\h5.3.3垃圾回收器的类型\h5.3.4评价GC策略的指标\h5.3.5新生代串行回收器\h5.3.6老年代串行回收器\h5.3.7并行回收器\h5.3.8新生代并行回收器\h5.3.9老年代并行回收器\h5.3.10CMS回收器\h5.3.11G1回收器\h5.3.12StoptheWorld案例\h5.3.13垃圾回收器对系统性能的影响\h5.3.14GC操作相关参数总结\h5.4常用调优案例和方法\h5.4.1将新对象预留在新生代\h5.4.2大对象进入老年代\h5.4.3设置对象进入老年代的年龄\h5.4.4稳定与振荡的堆大小\h5.4.5吞吐量优先案例\h5.4.6使用大页案例\h5.4.7降低停顿案例\h5.5实用JVM参数\h5.5.1JIT编译参数\h5.5.2堆快照\h5.5.3错误处理\h5.5.4获取GC信息\h5.5.5类和对象跟踪\h5.5.6控制GC\h5.5.7选择类校验器\h5.5.8Solaris下的线程控制\h5.5.9使用大页\h5.5.10压缩指针\h5.6JVM调优实战\h5.6.1Tomcat简介与启动加速\h5.6.2Web应用程序简介\h5.6.3JMeter简介与使用\h5.6.4调优前Web应用运行状况\h5.6.5调优过程\h5.7小结\h第6章Java性能调优工具\h6.1Linux命令行工具\h6.1.1top命令\h6.1.2sar命令\h6.1.3vmstat命令\h6.1.4iostat命令\h6.1.5pidstat工具\h6.2Windows工具\h6.2.1任务管理器\h6.2.2perfmon性能监控工具\h6.2.3ProcessExplorer工具\h6.2.4pslist命令行\h6.3JDK命令行工具\h6.3.1jps命令\h6.3.2jstat命令\h6.3.3jinfo命令\h6.3.4jmap命令\h6.3.5jhat命令\h6.3.6jstack命令\h6.3.7jstatd命令\h6.3.8hprof工具\h6.3.9jcmd命令\h6.4JConsole工具\h6.4.1JConsole连接Java程序\h6.4.2Java程序概况\h6.4.3内存监控\h6.4.4线程监控\h6.4.5类加载情况\h6.4.6虚拟机信息\h6.4.7MBean管理\h6.4.8使用插件\h6.5VisualVM多合一工具\h6.5.1VisualVM连接应用程序\h6.5.2监控应用程序概况\h6.5.3ThreadDump和分析\h6.5.4性能分析\h6.5.5快照\h6.5.6内存快照分析\h6.5.7MBean管理功能\h6.5.8TDA的使用\h6.5.9BTrace简介\h6.6VisualVM对OQL的支持\h6.6.1VisualVM的OQL基本语法\h6.6.2内置heap对象\h6.6.3对象函数\h6.6.4集合/统计函数\h6.6.5程序化OQL\h6.7MAT内存分析工具\h6.7.1初识MAT\h6.7.2浅堆和深堆\h6.7.3支配树\h6.7.4垃圾回收根\h6.7.5内存泄漏检测\h6.7.6最大对象报告\h6.7.7查找支配者\h6.7.8线程分析\h6.7.9集合使用情况分析\h6.7.10扩展MAT\h6.8MAT对OQL的支持\h6.8.1Select子句\h6.8.2From子句\h6.8.3Where子句\h6.8.4内置对象与方法\h6.9来自JRockit的礼物——JMC\h6.9.1得到JFR文件\h6.9.2Java程序的整体运行情况\h6.9.3CPU分析\h6.9.4内存分析\h6.9.5I/O分析\h6.10小结第1章Java性能调优概述本章将对性能优化技术进行整体性概述,让读者了解性能的概念和性能优化的基本思路和方法。掌握这些内容,有助于读者对性能问题进行系统分析。本章涉及的主要知识点有:·评价性能的主要指标;·木桶原理的概念及其在性能优化中的应用;·Amdahl定律的含义;·性能调优的层次;·系统优化的一般步骤和注意事项。1.1性能概述为什么程序总是那么慢?它现在到底在干什么?时间都耗费在哪里了?也许,你经常会抱怨这些问题。如果是这样,那么说明你的程序出现了性能问题。和功能性问题相比,性能问题在有些情况下可能并不算什么太大的问题,将就将就,也就过去了。但是,严重的性能问题会导致程序瘫痪、假死,直至崩溃。本节就先来认识性能的各种表现和指标。1.1.1看懂程序的性能对于客户端程序而言,拙劣的性能会严重影响用户体验。界面停顿、抖动、响应迟钝等问题会遭到用户不停地抱怨。一个典型的例子就是EclipseIDE工具在执行FullGC时会出现程序“假死”现象,相信这一定被不少开发人员所诟病。对于服务器程序来说,性能问题则更为重要。相信不少后台服务器软件都有各自的性能目标,以Web服务器为例,服务器的响应时间和吞吐量就是两个重要的性能参数。当服务器承受巨大的访问压力时,可能出现响应时间变长、吞吐量下降,甚至抛出内存溢出异常而崩溃等问题。这些问题,都是性能调优需要解决的。一般来说,程序的性能可以有以下几个方面的表现。·执行速度:程序的反应是否迅速,响应时间是否足够短。·内存分配:内存分配是否合理,是否过多地消耗内存或者存在泄漏。·启动时间:程序从运行到可以正常处理业务需要花费多长时间。·负载承受能力:当系统压力上升时,系统的执行速度和响应时间的上升曲线是否平缓。1.1.2性能的参考指标为了能够科学地进行性能分析,对性能指标进行定量评测是非常重要的。目前,可以用于定量评测的性能指标有:·执行时间:一段代码从开始运行到运行结束所使用的时间。·CPU时间:函数或者线程占用CPU的时间。·内存分配:程序在运行时占用的内存空间。·磁盘吞吐量:描述I/O的使用情况。·网络吞吐量:描述网络的使用情况。·响应时间:系统对某用户行为或者事件做出响应的时间。响应时间越短,性能越好。1.1.3木桶原理与性能瓶颈木桶原理又称短板理论,其核心思想是一只木桶盛水的多少,并不取决于桶壁上最高的那块木板,而是取决于桶壁上最短的那块木板,如图1.1所示。图1.1木桶原理示意图将这个理论应用到系统性能优化上可以这么理解,即使系统拥有充足的内存资源和CPU资源,但是,如果磁盘I/O性能低下,那么系统的总体性能是取决于当前最慢的磁盘I/O速度,而不是当前最优越的CPU或者内存。在这种情况下,如果需要进一步提升系统性能,优化内存或者CPU资源是毫无用处的,只有提高磁盘I/O性能才能对系统的整体性能进行优化。此时,磁盘I/O就是系统的性能瓶颈。注意:根据木桶原理,系统的最终性能取决于系统中性能表现最差的组件。因此,为了提升系统的整体性能,必须对系统中表现最差的组件进行优化,而不是对系统中表现良好的组件进行优化。根据应用的特点不同,任何计算机资源都有可能成为系统瓶颈。其中,最有可能成为系统瓶颈的计算资源有:·磁盘I/O:由于磁盘I/O读写的速度比内存慢很多,程序在运行过程中,如果需要等待磁盘I/O完成,那么低效的I/O操作会拖累整个系统。·网络操作:对网络数据进行读写的情况与磁盘I/O类似,由于网络环境的不确定性,尤其是对互联网上数据的读写,网络操作的速度可能比本地磁盘I/O更慢,因此,如不加特殊处理,也极可能成为系统瓶颈。·CPU:对计算资源要求较高的应用,由于其长时间、不间断地大量占用CPU资源,那么对CPU的争夺将导致性能问题。例如,科学计算、3D渲染等对CPU需求量大的应用便是如此。·异常:对Java应用来说,异常的捕获和处理是非常消耗资源的。如果程序高频率地进行异常处理,则整体性能便会有明显下降。·数据库:大部分应用程序都离不开数据库,而海量数据的读写操作往往是相当费时的。应用程序需要等待数据库操作完成并返回结果,那么缓慢的同步操作将成为系统瓶颈。·锁竞争:对高并发程序来说,如果存在激烈的锁竞争,对性能无疑是极大的打击。锁竞争将会明显增加线程上下文切换的开销,而且这些开销都是与应用需求无关的系统开销,白白占用宝贵的CPU资源,却不带来任何好处。·内存:一般来说,只要应用程序设计合理,内存在读写速度上不太可能成为性能瓶颈。除非应用程序进行了高频率的内存交换和扫描,但这种情况比较少见。内存制约系统性能最有可能出现的情况是内存容量不足。与磁盘相比,内存的容量似乎小得可怜,这意味着应用软件只能尽可能将常用的核心数据读入内存,这在一定程度上降低了系统性能。1.1.4Amdahl定律Amdahl定律是计算机科学中非常重要的定律,它定义了串行系统并行化后的加速比的计算公式和理论上限。加速比定义如下:加速比=优化前系统耗时/优化后系统耗时即所谓的加速比,就是优化前系统耗时与优化后系统耗时的比值。加速比越高,表明优化效果越明显。Amdahl定律给出了加速比与系统并行度和处理器数量的关系。设加速比为Speedup,系统内必须串行化的程序比重为F,CPU处理器的数量为N,则有根据这个公式可知,如果CPU处理器的数量趋于无穷,那么加速比与系统的串行化率成反比,如果系统中必须有50%的代码串行执行,那么系统的最大加速比为2。假设有一程序分为5个步骤执行,每个执行步骤花费100个时间单位。其中,只有步骤2和步骤5可以进行并行,步骤1、3、4必须串行,如图1.2所示。在全串行的情况下,系统合计耗时500个时间单位。图1.2串行工作流程若将步骤2和步骤5并行化,假设在双核处理器上,则有如图1.3所示的处理流程。在这种情况下,步骤2和步骤5的耗时将为50个时间单位,故系统整体耗时为400个时间单位。根据加速比的定义有:加速比=优化前系统耗时/优化后系统耗时=500/400=1.25或者使用Amdahl定律给出的加速比公式。由于5个步骤中,3个步骤必须串行,因此其串行化比重为3/5=0.6,即F=0.6,且双核处理器的处理器个数N为2。代入公式得:图1.3双核处理器上的并行化在极端情况下,假设并行处理器个数为无穷大,则有如图1.4所示的处理过程。步骤2和步骤5的处理时间趋于0。即使这样,系统整体耗时依然大于300个时间单位,即加速比的极限为500/300=1.67。使用加速比计算公式,N趋于无穷大,有Speedup=1/F,且F=0.6,故有Speedup=1.67。图1.4极端情况下的并行化由此可见,为了提高系统的速度,仅增加CPU处理器的数量并不一定能起到有效的作用。需要从根本上修改程序的串行行为,提高系统内可并行化的模块比重,在此基础上合理增加并行处理器的数量,才能以最小的投入得到最大的加速比。注意:根据Amdahl定律,使用多核CPU对系统进行优化,优化的效果取决于CPU的数量及系统中串行化程序的比重。CPU数量越多,串行化比重越低,则优化效果越好。仅提高CPU数量而不降低程序的串行化比重,则无法提高系统性能。1.2性能调优的层次为了提升系统性能,开发人员可以从系统的各个角度和层次对系统进行优化。除了最常见的代码优化外,在软件架构、JVM虚拟机、数据库及操作系统几个层面可以通过各种手段进行调优,从而在整体上提升系统的性能。1.2.1设计调优设计调优处于所有调优手段的上层,它往往需要在软件开发之前进行。在软件开发之初,软件架构师就应该评估系统可能存在的各种潜在问题,并给出合理的设计方案。由于软件设计和架构对软件整体质量有决定性的影响,所以,设计调优对系统性能的影响也是最大的。如果说代码优化、JVM优化都是对系统在微观层面上“量”的优化,那么设计优化就是对系统在宏观层面上“质”的优化。设计优化的一大显著特点是,它可以规避某一个组件的性能问题,而非改良该组件的实现。例如,系统中组件A需要等待某事件E才能触发一个行为。如果组件A通过循环监控不断监测事件E是否发生,其监测行为必然会占用部分系统资源,因此,开发人员必须在监测频率和资源消耗间取得平衡。如果监测频率太低,虽然减少了资源消耗,但是系统实时反应性就会降低。如果进行代码层的调优,就需要优化监测方法的实现以及求得一个最为恰当的监测频率。而若将此问题预留在设计层解决,便可以使用事件通知的方式将系统行为进行倒置。例如,使用将在第2章中提到的观察者模式,在事件E发生的时刻,由事件E通知组件A,从而触发组件A的行为。这种设计方法弃用了存在性能隐患的循环监控,从根本上解决了这一问题。从某种程度上说,设计优化直接决定了系统的整体品质。如果在设计层考虑不周,留下太多问题隐患,那么这些“质”上的问题,也许无法再通过代码层的优化进行弥补。因此,开发人员必须在软件设计之初,要认真、仔细地考虑软件系统的性能问题。进行设计优化时,设计人员必须熟悉常用的软件设计方法、设计模式、基本性能组件和常用的优化思想,并将其有机地集成在软件系统中。注意:一个良好的系统设计可以规避很多潜在的性能问题。因此,尽可能多花一些时间在系统设计上,这是创建高性能程序的关键。1.2.2代码调优代码调优是在软件开发过程中或者在软件开发完成后,软件维护过程中进行的对程序代码的改进和优化。代码优化涉及诸多编码技巧,需要开发人员熟悉相关语言的API,并在合适的场景中正确使用相关API或类库。同时,对算法和数据结构的灵活使用,也是代码优化的重要内容。虽然代码优化是从微观上对性能进行调整,但是一个“好”的实现和一个“坏”的实现对系统的影响也是非常大的。例如,同样作为List的实现,LinkedList和ArrayList在随机访问上的性能却相差几个数量级。又如,同样是文件读写的实现,使用Stream方式与JavaNIO方式,其性能可能又会相差一个数量级。因此,与设计优化相比,虽然笔者将代码优化称为在微观层面上的优化,但它却是对系统性能产生最直接影响的优化方法。1.2.3JVM调优由于Java软件总是运行在JVM虚拟机之上,因此对JVM虚拟机进行优化也能在一定程度上提升Java程序的性能。JVM调优通常可以在软件开发后期进行,如在软件开发完成或者在软件开发的某一里程碑阶段进行。作为Java软件的运行平台,JVM的各项参数如JVM的堆大小和垃圾回收策略等,将会直接影响Java程序的性能。要进行JVM层面的调优,需要开发人员对JVM的运行原理和基本内存结构有一定了解,例如堆内存的结构、GC的种类等,然后依据应用程序的特点,设置合理的JVM启动参数。1.2.4数据库调优对绝大部分应用系统而言,数据库是必不可少的一部分。Java程序可以使用JDBC的方式连接数据库。对数据库的调优可以分为以下3个部分:·在应用层对SQL语句进行优化;·对数据库进行优化;·对数据库软件进行优化。在应用层优化数据库访问,涉及大量的编程技巧。例如,当使用JDBC进行查询时,对于大量拥有相同结构的SQL查询,可以使用PreparedStatement代替Statement,提高数据库的查询效率;在Select语句中,显示指定要查询的列名,避免使用星号“*”。在对数据库进行优化时,主要目的是建立一个具有良好表结构的数据库。例如,为了提高多表级联查询效率,可以合理地使用冗余字段;对于大表,可以使用行的水平切割或者类似于Oracle分区表的技术;为了提高数据库的查询效率,可以建立有效且合理的索引。对于数据库软件的优化,根据不同的数据库,如Oracle、MySQL或SQLServer,都拥有不同的方式。以Oracle为例,设置合理大小的共享池、缓存缓冲区或者PGA,对Oracle的运行性能都有很大的影响。鉴于本书的讨论范围,数据库优化将不作为本书的阐述重点。1.2.5操作系统调优作为软件运行的基础平台,操作系统的性能对应用系统也有较大的影响。不同类型的操作系统,调优的手段和参数可能会有所不同。例如,在主流UNIX系统中,共享内存段、信号量、共享内存最大值(shmmax)、共享内存最小值(shmmin)等都是可以进行优化的系统资源。此外,如最大文件句柄数、虚拟内存大小、磁盘的块大小等参数都可能对软件的性能产生影响。图1.5展示了在Windows平台上配置虚拟内存的界面。图1.5在Windows上设置虚拟内存说明:操作系统的性能调优不在本书的讨论范围内,有兴趣的读者可以参考相关书籍。1.3基本调优策略和手段存在性能问题的系统,十有八九是由某一系统瓶颈导致的。只要找到该性能瓶颈,分析瓶颈的形成原因,对症下药,使用合理的方法解决系统瓶颈,就能从根本上提升性能。所以,系统性能优化的最主要目的就是查找并解决性能瓶颈问题。但值得注意的是,性能优化往往会涉及对原有的实现进行较大的修改,因此很难保证这些修改不引发新的问题。所以,在性能优化前,需要对性能优化的目标和使用的方法进行统筹安排。1.3.1优化的一般步骤对软件系统进行优化,首先需要有明确的性能目标,清楚地指出优化的对象和最终目的。其次,需要在目标平台上对软件进行测试,通过各种性能监控和统计工具,观测和确认当前系统是否已经达到相关目标,若已经达到,则没有必要再进行优化;若尚未达到优化目标,则需要查找当前的性能瓶颈。可能成为性能瓶颈的因素很多,如磁盘I/O、网络I/O和CPU。当找到性能瓶颈后,首先需要定位相关代码,确认是否在软件实现上存在问题或者具有优化的空间。若存在优化空间,则进行代码优化;否则需要考虑进行JVM层、数据库层或者操作系统的优化,甚至可以考虑修改原有设计,或者提升硬件性能。当优化完成后,需要在目标平台上进行确认测试。若达到性能目标,则优化过程结束;否则需要再次查找系统瓶颈,如此反复,如图1.6所示。图1.6性能优化的一般步骤1.3.2系统优化的注意事项性能优化虽然能提升软件的性能,但是优化过程往往伴随着一些风险和弊端。例如,为了优化某一段代码的实现,就需要重写原有的算法,而这就很可能引入新的Bug。重新实现新的功能模块同时也意味着需要重新对其进行完整的功能性测试,使优化前所做的测试工作变得毫无意义。而且,优化后的代码与优化前的代码相比,可能会比较晦涩难懂,在一定程度上影响了系统的可维护性。因此,软件优化需要在软件功能、正确性和可维护性之间取得平衡,而不应该过分地追求软件性能。在进行优化前,必须要有明确的已知问题和性能目标,决不可为了“优化”而“优化”。因此在动手前,必须知道自己要干什么。任何优化都是为了解决具体的软件问题,如果软件已经可以正常工作,在没有性能问题暴露前,只是凭着主观臆断对某些模块进行性能改进,从软件规范化开发的角度来说是非常冒险的。因为修改后的新代码没有经过完整的测试,软件质量就没有保障。而且,优化后的性能提升幅度可能并不足以让开发者值得如此费尽心机。因此,在进行软件优化时,必须要进行慎重的评估。注意:性能调优必须有明确的目标,不要为了调优而调优。如果当前程序并没有明显的性能问题,盲目地进行调优,其风险可能远远大于收益。1.4小结通过本章的学习,读者应该了解性能的基本概念及常用的参考指标。此外,本章还较为详细地介绍了与性能调优相关的两个重要理论——木桶原理和Amdahl定律。根据木桶原理,系统的最终性能总是由系统中性能最差的组件决定的,因此,改善该组件的性能对提升系统整体性能有重要的作用。而根据Amdahl定律可以知道,只是增加处理器数量对提升系统性能并没有太大的实际意义,还必须同时提高程序的并行化比重。本章还简要介绍了在软件开发和维护过程中可以进行性能优化的各个阶段。例如,在软件的设计阶段,需要选用合理的软件结构和性能组件;在编码阶段,需要提高代码的执行效率;对于Java应用程序,在系统的运行期,还需要设置合理的JVM虚拟机参数;同时,优化数据库和操作系统也对系统整体性能有直接影响。在本章的最后还简要介绍了性能优化的一般步骤和注意事项。第2章设计优化本章主要介绍与软件设计相关的性能优化方法和思想。软件的结构对系统的整体性能有着重要的影响,优秀的设计结构可以规避很多潜在的性能问题,对系统性能的影响可能远远大于对代码的优化。因此,熟悉一些常用的软件设计模式和方法,对设计高性能软件有很大帮助。本章着眼于设计优化,主要讲解一些与性能相关的常用设计模式、组件和设计方法。本章涉及的主要知识点有:·单例模式的使用和实现;·代理模式的实现和深入剖析;·享元模式的应用;·装饰者模式对性能组件的封装;·观察者模式的使用;·使用值对象模式减少网络数据传输;·使用业务代理模式添加远程调用缓存;·缓冲和缓存的定义与使用;·对象池的使用场景及其基本实现;·负载均衡系统的构建及Terracotta框架的简单使用;·时间换空间和空间换时间的基本思路。2.1善用设计模式设计模式是前人工作的总结和提炼。通常,被人们广泛流传的设计模式都是对某一特定问题的成熟解决方案。如果能合理地使用设计模式,不仅能使系统更容易被他人理解,同时也能使系统拥有更加合理的结构。本节总结归纳一些经典的设计模式,并详细说明它们与软件性能之间的关系。2.1.1单例模式单例模式是设计模式中使用最为普遍的模式之一,它是一种对象创建模式,用于产生一个对象的具体实例,可以确保系统中一个类只产生一个实例。在Java语言中,这样的行为能带来两大好处:(1)对于频繁使用的对象,可以省去new操作花费的时间,这对于那些重量级对象而言,是一笔非常可观的系统开销。(2)由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。因此对于系统的关键组件和被频繁使用的对象,使用单例模式可以有效地改善系统的性能。单例模式的角色非常简单,只有单例类和使用者两个,如表2.1所示。表2.1单例模式的角色单例模式的基本结构如图2.1所示。图2.1单例模式的结构单例模式的核心在于通过一个接口返回唯一的对象实例。一个简单的单例实现如下:publicclassSingleton{
privateSingleton(){
System.out.println("Singletoniscreate");//创建单例的过程可能会比较慢
}
privatestaticSingletoninstance=newSingleton();
publicstaticSingletongetInstance(){
returninstance;
}
}注意代码中的重点标注部分,首先单例类必须要有一个private访问级别的构造函数,只有这样,才能确保单例不会在系统的其他代码内被实例化,这一点是相当重要的。其次,instance成员变量和getInstance()方法必须是static的。注意:单例模式是一种非常常用的结构,几乎所有的系统中都可以找到它的身影。因此,希望读者通过本节的学习,了解单例模式的几种实现方式及各自的特点。这种单例的实现方式非常简单,而且十分可靠,唯一的不足仅是无法对instance做延迟加载。假如单例的创建过程很慢,而由于instance成员变量是static定义的,因此在JVM加载单例类时,单例对象就会被建立,如果此时这个单例类在系统中还扮演其他角色,那么在任何使用这个单例类的地方都会初始化这个单例变量,而不管是否会被用到。例如,单例类作为String工厂用于创建一些字符串(该类既用于创建单例,又用于创建String对象):publicclassSingleton{
privateSingleton(){
//创建单例的过程可能会比较慢
System.out.println("Singletoniscreate");
}
privatestaticSingletoninstance=newSingleton();
publicstaticSingletongetInstance(){
returninstance;
}
publicstaticvoidcreateString(){//这是模拟单例类扮演其他角色
System.out.println("createStringinSingleton");
}
}当使用Singleton.createString()执行任务时,程序输出以下内容:Singletoniscreate
createStringinSingleton可以看到,虽然此时并没有使用单例类,但它还是被创建出来,这也许是开发人员所不愿意见到的。为了解决这个问题,并提高系统在相关函数调用时的反应速度,就需要引入延迟加载机制。publicclassLazySingleton{
privateLazySingleton(){
//创建单例的过程可能会比较慢
System.out.println("LazySingletoniscreate");
}
privatestaticLazySingletoninstance=null;
publicstaticsynchronizedLazySingletongetInstance(){
if(instance==null)
instance=newLazySingleton();
returninstance;
}
}首先,对于静态成员变量instance赋予初始值null,确保系统启动时没有额外的负载。其次,在getInstance()工厂方法中,判断当前单例是否已经存在,若存在,则返回,不存在,再建立单例。这里尤其要注意,getInstance()方法必须是同步的,否则在多线程环境下,当线程1正新建单例完成赋值操作前,线程2可能判断instance为null,故线程2也将启动新建单例的程序,从而导致多个实例被创建,因此同步关键字是必需步骤。使用上例中的单例,虽然实现了延迟加载的功能,但和第一种方法相比,它引入了同步关键字,因此在多线程环境中,它的时耗要远远大于第一种单例模式的时耗。以下测试代码就说明了这个问题。@Override
publicvoidrun(){
for(inti=0;i<100000;i++)
Singleton.getInstance();
//LazySingleton.getInstance();
System.out.println("spend:"+(System.currentTimeMillis()-begintime));
}开启5个线程同时完成以上代码的运行,使用第一种单例耗时0ms,而使用LazySingleton却相对耗时约390ms,性能至少相差2个数量级。注意:在本书中,会使用很多类似的代码片段来测试不同代码的执行速度,在不同的计算机上其测试结果很可能与笔者不同。读者大可不必关心测试数据的绝对值,而只要观察用于比较的目标代码间的相对耗时即可。为了使用延迟加载引入的同步关键字反而降低了系统性能,是不是有点得不偿失呢?为了解决这个问题,还需要对其进行以下改进:publicclassStaticSingleton{
privateStaticSingleton(){
System.out.println("StaticSingletoniscreate");
}
privatestaticclassSingletonHolder{
privatestaticStaticSingletoninstance=newStaticSingleton();
}
publicstaticStaticSingletongetInstance(){
returnSingletonHolder.instance;
}
}在这个实现中,单例模式使用内部类来维护单例的实例,当StaticSingleton被加载时,其内部类并不会被初始化,故可以确保当StaticSingleton类被载入JVM时不会初始化单例类,而当getInstance()方法被调用时才会加载SingletonHolder,从而初始化instance。同时,由于实例的建立是在类加载时完成的,故天生对多线程友好,getInstance()方法也不需要使用同步关键字。因此,这种实现方式同时兼备以上两种实现方式的优点。注意:使用内部类的方式实现单例,既可以做到延迟加载,也不必使用同步关键字,是一种比较完善的实现方式。通常情况下,用以上方式实现的单例已经可以确保在系统中只存在唯一实例了。但仍然有例外情况——可能导致系统生成多个实例,例如在代码中通过反射机制,强行调用单例类的私有构造函数生成多个单例。考虑到情况的特殊性,本书不对这种极端的方式进行讨论。但仍有些合法的方法,可能导致系统出现多个单例类的实例。以下是一个可以被串行化的单例:publicclassSerSingletonimplementsjava.io.Serializable{
Stringname;
privateSerSingleton(){
//创建单例的过程可能会比较慢
System.out.println("Singletoniscreate");
name="SerSingleton";
}
privatestaticSerSingletoninstance=newSerSingleton();
publicstaticSerSingletongetInstance(){
returninstance;
}
publicstaticvoidcreateString(){
System.out.println("createStringinSingleton");
}
privateObjectreadResolve(){//阻止生成新的实例,总是返回当前对象
returninstance;
}
}测试代码如下:@Test
publicvoidtest()throwsException{
SerSingletons1=null;
SerSingletons=SerSingleton.getInstance();
//先将实例串行化到文件
FileOutputStreamfos=newFileOutputStream("SerSingleton.txt");
ObjectOutputStreamoos=newObjectOutputStream(fos);
oos.writeObject(s);
oos.flush();
oos.close();
//从文件读出原有的单例类
FileInputStreamfis=newFileInputStream("SerSingleton.txt");
ObjectInputStreamois=newObjectInputStream(fis);
s1=(SerSingleton)ois.readObject();
Assert.assertEquals(s,s1);
}使用一段测试代码测试单例的串行化和反串行化,当去掉SerSingleton代码中加粗的readResolve()函数时,测试代码抛出以下异常:junit.framework.AssertionFailedError:expected:javatuning.ch2.singleton.serialization.SerSingleton@5224ee
butwas:javatuning.ch2.singleton.serialization.SerSingleton@18fe7c3这说明测试代码中的s和s1指向了不同的实例,在反序列化后生成了多个对象实例。而加上readResolve()函数的程序则正常退出,这说明即便经过反序列化,也仍然保持单例的特征。事实上,在实现了私有的readResolve()方法后,readObject()方法已经形同虚设,它直接使用readResolve()替换了原本的返回值,从而在形式上构造了单例。注意:序列化和反序列化可能会破坏单例。一般来说,对单例进行序列化和反序列化的场景并不多见,但如果存在,就要多加注意。2.1.2代理模式代理模式也是一种很常见的设计模式,它使用代理对象完成用户请求,屏蔽用户对真实对象的访问。就如同现实中的代理一样,代理人被授权执行当事人的一些事宜,而无须当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上,代理人要有当事人的授权,并且在核心问题上还需要请示当事人。在现实中,使用代理的情况很普遍,而且原因也很多。例如:当事人因为某些隐私不方便出面,或者当事人不具备某些相关的专业技能,而需要一个专业人员来完成一些专业的操作;由于当事人没有时间处理事务,而聘用代理人出面。在软件设计中,使用代理模式的意图也很多,例如,出于安全原因,需要屏蔽客户端直接访问真实对象;在远程调用中,需要使用代理类处理远程方法调用的技术细节(如RMI);为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。在本小节中,主要讨论使用代理模式实现延迟加载,从而提升系统的性能和反应速度。1.代理模式的结构代理模式的主要角色有4个,如表2.2所示。表2.2代理模式的角色下面以一个简单的示例来阐述使用代理模式实现延迟加载的方法及其意义。假设某客户端软件有根据用户请求去数据库查询数据的功能,在查询数据前需要获得数据库连接,软件开启时初始化系统的所有类,此时尝试获得数据库连接。当系统有大量的类似操作(如xml解析等)存在时,所有这些初始化操作的叠加会使得系统的启动速度变得非常缓慢。为此,可以使用代理模式的代理类封装对数据库查询中的初始化操作,当系统启动时初始化这个代理类,而非初始化真实的数据库查询类,在此过程中代理类什么都没有做,因此它的构造是相当迅速的。在系统启动时,将消耗资源最多的方法都使用代理模式分离,这样就可以加快系统的启动速度,从而减少用户的等待时间。而在用户真正做查询操作时,再由代理类单独去加载真实的数据库查询类,从而完成用户的请求。这个过程就是使用代理模式实现了延迟加载。注意:代理模式可以用于多种场合,如用于远程调用的网络代理,以及考虑安全因素的安全代理等。延迟加载只是代理模式的一种应用场景。延迟加载的核心思想是:如果当前并没有使用这个组件,则不需要真正地初始化它,而是使用一个代理对象替代它原有的位置,只有在真正需要使用的时候,才对它进行加载。使用代理模式的延迟加载是非常有意义的:首先,它可以在时间轴上分散系统的压力,尤其是在系统启动时,不必完成所有的初始化工作,从而减少启动时间;其次,对于很多真实主题而言,可能在软件启动直到关闭的整个过程中根本不会被调用,因此初始化这些数据无疑是一种资源浪费。图2.2显示了使用代理类封装数据库查询类后系统的启动过程。图2.2代理类的工作流程若系统不使用代理模式,则在启动时就要初始化DBQuery对象,而使用代理模式后,启动时只需要初始化一个轻量级的对象DBQueryProxy即可。系统结构如图2.3所示。IDBQuery是主题接口,定义代理类和真实类需要对外提供的服务,在本例中定义了实现数据库查询的公共方法,即request()函数。DBQuery是真实主题,负责实际的业务操作,DBQueryProxy是DBQuery的代理类。图2.3代理模式的一种实现2.代理模式的实现和使用基于以上设计,IDBQuery的实现方式如下。它只有一个request()方法。publicinterfaceIDBQuery{
Stringrequest();
}DBQuery的实现方式如下。它是一个重量级对象,构造会比较慢。publicclassDBQueryimplementsIDBQuery{
publicDBQuery(){
try{
Thread.sleep(1000);//可能包含数据库连接等耗时操作
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
@Override
publicStringrequest(){
return"requeststring";
}
}代理类DBQueryProxy是轻量级对象,创建很快,用于替代DBQuery的位置。publicclassDBQueryProxyimplementsIDBQuery{
privateDBQueryreal=null;
@Override
publicStringrequest(){
//在真正需要的时候才创建真实的对象,创建过程可能很慢
if(real==null)
real=newDBQuery();
//在多线程环境下,返回一个虚假类,类似于Future模式
returnreal.request();
}
}最后,主函数如下。它引用IDBQuery接口,并使用代理类工作。publicclassMain{
publicstaticvoidmain(Stringargs[]){
IDBQueryq=newDBQueryProxy();//使用代理
q.request();//在真正使用时才创建真实对象
}
}注意:将代理模式用于实现延迟加载,可以有效地提升系统的启动速度,对改善用户体验有很大的帮助。3.动态代理介绍动态代理是指在运行时动态生成代理类,即代理类的字节码将在运行时生成并载入当前的ClassLoader。与静态代理类相比,动态类有诸多好处。首先,不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也是非常烦人的事,如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;其次,使用一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。注意:动态代理使用字节码动态生成加载技术,在运行时生成并加载类。生成动态代理类的方法很多,如JDK自带的动态代理、CGLIB、Javassist或者ASM库。JDK的动态代理使用简单,内置在JDK中,因此不需要引入第三方Jar包,但功能相对比较弱。CGLIB和Javassist都是高级字节码生成库,总体性能比JDK自带的动态代理好,而且功能十分强大。ASM是低级字节码生成工具,使用ASM已经近乎于在使用Java字节码编程,对开发人员要求最高,当然也是一种性能最好的动态代理生成工具。但ASM的使用实在过于烦琐,而且性能也没有数量级的提升,与CGLIB等高级字节码生成工具相比,ASM程序的可维护性也较差,如果不是对性能有苛刻要求的场合,笔者还是推荐使用CGLIB或者Javassist。4.动态代理实现仍以DBQueryProxy为例,使用动态代理生成动态类,替代上例中的DBQueryProxy。首先,使用JDK的动态代理生成代理对象。JDK的动态代理需要实现一个处理方法调用的Handler,用于实现代理方法的内部逻辑。publicclassJdkDbQueryHandlerimplementsInvocationHandler{
IDBQueryreal=null;//主题接口
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
throwsThrowable{
if(real==null)
real=newDBQuery();//如果是第一次调用,则生成真实对象
returnreal.request();//使用真实主题完成实际操作
}
}以上代码实现了一个Handler。可以看到,它的内部逻辑和DBQueryProxy是类似的。在调用真实主题的方法前,先尝试生成真实主题对象,接着需要使用这个Handler生成动态代理对象,代码如下:publicstaticIDBQuerycreateJdkProxy(){
IDBQueryjdkProxy=(IDBQuery)Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
newClass[]{IDBQuery.class},
newJdkDbQueryHandler());//指定Handler
returnjdkProxy;
}以上代码生成一个实现了IDBQuery接口的代理类,代理类的内部逻辑由JdkDbQuery-Handler决定。生成代理类后,由newProxyInstance()方法返回该代理类的一个实例。至此,一个完整的JDK动态代理就完成了。CGLIB和Javassist的使用和JDK的动态代理的使用非常类似,下面尝试使用CGLIB生成动态代理。CGLIB也需要实现一个处理代理逻辑的切入类,代码如下:publicclassCglibDbQueryInterceptorimplementsMethodInterceptor{
IDBQueryreal=null;
@Override
publicObjectintercept(Objectarg0,Methodarg1,Object[]arg2,
MethodProxyarg3)throwsThrowable{
if(real==null)//代理类的内部逻辑
//和前文中的一样
real=newDBQuery();
returnreal.request();
}
}在这个切入对象的基础上可以生成动态代理,代码如下:publicstaticIDBQuerycreateCglibProxy(){
Enhancerenhancer=newEnhancer();
//指定切入器,定义代理类逻辑
enhancer.setCallback(newCglibDbQueryInterceptor());
//指定实现的接口
enhancer.setInterfaces(newClass[]{IDBQuery.class});
//生成代理类的实例
IDBQuerycglibProxy=(IDBQuery)enhancer.create();
returncglibProxy;
}使用Javassist生成动态代理有两种方式,一种是使用代理工厂创建,另一种是使用动态代码创建。使用代理工厂创建时,方法与CGLIB类似,也需要实现一个用于处理代理逻辑的Handler,代码如下:publicclassJavassistDynDbQueryHandlerimplementsMethodHandler{
IDBQueryreal=null;
@Override
publicObjectinvoke(Objectarg0,Methodarg1,Methodarg2,Object[]arg3)
throwsThrowable{
if(real==null)
real=newDBQuery();
returnreal.request();
}
}以这个Handler为基础,创建动态Javasssit代理,代码如下:publicstaticIDBQuerycreateJavassistDynProxy()throwsException{
ProxyFactoryproxyFactory=newProxyFactory();
//指定接口
proxyFactory.setInterfaces(newClass[]{IDBQuery.class});
ClassproxyClass=proxyFactory.createClass();
IDBQueryjavassistProxy=(IDBQuery)proxyClass.newInstance();
//设置Handler
((ProxyObject)javassistProxy).setHandler(newJavassistDynDbQueryHandler());
returnjavassistProxy;
}Javassist使用动态Java代码创建代理的过程和前文的方法略有不同。Javassist内部可以通过动态Java代码生成字节码,这种方式创建的动态代理非常灵活,甚至可以在运行时生成业务逻辑。publicstaticIDBQuerycreateJavassistBytecodeDynamicProxy()throwsException{
ClassPoolmPool=newClassPool(true);
//定义类名
CtClassmCtc=mPool.makeClass(IDBQuery.class.getName()+"Javassist
BytecodeProxy");
//需要实现的接口
mCtc.addInterface(mPool.get(IDBQuery.class.getName()));
//添加构造函数
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
//添加类的字段信息,使用动态Java代码
mCtc.addField(CtField.make("public"+IDBQuery.class.getName()+
"real;",mCtc));
Stringdbqueryname=DBQuery.class.getName();
//添加方法,这里使用动态Java代码指定内部逻辑
mCtc.addMethod(CtNewMethod.make("publicStringrequest(){if(real==
null)real=new"+dbqueryname+"();returnreal.request();}",mCtc));
//基于以上信息生成动态类
Classpc=mCtc.toClass();
//生成动态类的实例
IDBQuerybytecodeProxy=(IDBQuery)pc.newInstance();
returnbytecodeProxy;
}在以上代码中,使用CtField.make()方法和CtNewMehod.make()方法在运行时分别生成了代理类的字段和方法。这些逻辑由Javassist的CtClass对象处理,将Java代码转换为对应的字节码,并生成动态代理类的实例。注意:与静态代理相比,动态代理可以大幅度地减少代码行数,并提升系统的灵活性。在Java中,动态代理类的生成主要涉及对ClassLoader的使用。这里以CGLIB为例,简要阐述动态类的加载过程。使用CGLIB生成动态代理,首先需要生成Enhancer类实例,并指定用于处理代理业务的回调类。在Enhancer.create()方法中,会使用DefaultGenerator-Strategy.Generate()方法生成动态代理类的字节码,并保存在byte数组中。接着使用ReflectUtils.define-Class()方法通过反射调用ClassLoader.defineClass()方法,将字节码装载到ClassLoader中,完成类的加载。最后使用ReflectUtils.newInstance()方法通过反射生成动态类的实例,并返回该实例。无论使用何种方法生成动态代理,虽然实现细节不同,但主要逻辑都如图2.4所示。图2.4实现动态代理的基本步骤前文介绍的几种动态代理的生成方法,性能有一定差异。为了能更好地测试它们的性能,去掉DBQuery类中的sleep()代码,并使用以下方法进行测试。publicstaticfinalintCIRCLE=30000000;
publicstaticvoidmain(String[]args)throwsException{
IDBQueryd=null;
longbegin=System.currentTimeMillis();
d=createJdkProxy();//测试JDK动态代理
System.out.println("createJdkProxy:"+(System.currentTimeMillis()-begin));
System.out.println("JdkProxyclass:"+d.getClass().getName());
begin=System.currentTimeMillis();
for(inti=0;i<CIRCLE;i++)
d.request();
System.out.println("callJdkProxy:"+(System.currentTimeMillis()-begin));
begin=System.currentTimeMillis();
d=createCglibProxy();//测试CGLIB动态代理
System.out.println("createCglibProxy:"+(System.currentTimeMillis()
-begin));
System.out.println("CglibProxyclass:"+d.getClass().getName());
begin=System.currentTimeMillis();
for(inti=0;i<CIRCLE;i++)
d.request();
System.out.println("callCglibProxy:"+(System.currentTimeMillis()
-begin));
begin=System.currentTimeMillis();
d=createJavassistDynProxy();//测试Javassist动态代理
System.out.println("createJavassistDynProxy:"+(System.currentTimeMillis()
-begin));
System.out.println("JavassistDynProxyclass:"+d.getClass().getName());
begin=System.currentTimeMillis();
for(inti=0;i<CIRCLE;i++)
d.request();
System.out.println("callJavassistDynProxy:"+(System.currentTimeMillis()
-begin));
begin=System.currentTimeMillis();
//测试Javassistbytecode动态代理
d=createJavassistBytecodeDynamicProxy();
System.out.println("createJavassistBytecodeDynamicProxy:"+(System.
currentTimeMillis()-begin));
System.out.println("JavassistBytecodeDynamicProxyclass:"+d.getClass().
getName());
begin=System.currentTimeMillis();
for(inti=0;i<CIRCLE;i++)
d.request();
System.out.println("callJavassistBytecodeDynamicProxy:"+(System.
currentTimeMillis()-begin));
}以上代码分别生成了4种代理,并对生成的代理类进行高频率的调用,最后输出各个代理类的创建耗时、动态类类名和方法调用耗时。结果如下:createJdkProxy:0
JdkProxyclass:$Proxy0
callJdkProxy:610
createCglibProxy:140
CglibProxyclass:$xy.IDBQuery$$EnhancerByCGLIB$$b75a4bbf
callCglibProxy:594
createJavassistDynProxy:47
JavassistDynProxyclass:xy.IDBQuery_$$_javassist_0
callJavassistDynProxy:1422
createJavassistBytecodeDynamicProxy:94
JavassistBytecodeDynamicProxyclass:xy.IDBQueryJavassistBytecodeProxy
callJavassistBytecodeDynamicProxy:562可以看到,JDK的动态类创建过程最快,这是因为在这个内置实现中,defineClass()方法被定义为native实现,故性能高于其他几种实现。但在代理类的函数调用性能上,JDK的动态代理不如CGLIB和Javassist的基于动态代码的代理,而Javassist的基于代理工厂的代理实现,代理的性能质量最差,甚至不如JDK的实现。在实际开发应用中,代理类的方法调用频率通常要远远高于代理类的实际生成频率(相同类的重复生成会使用Cache),故动态代理对象的方法调用性能应该作为性能的主要关注点。注意:就动态代理的方法调用性能而言,CGLIB和Javassist的基于动态代码的代理都优于JDK自带的动态代理。此外,JDK的动态代理要求代理类和真实主题都实现同一个接口,而CGLIB和Javassist则没有强制要求。5.Hibernate中代理模式的应用用代理模式实现延迟加载的一个经典应用就在Hibernate框架中。当Hibernate加载实体bean时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采取延迟加载的机制,以提高系统的性能。Hibernate中的延迟加载主要有两种:一是属性的延迟加载;二是关联表的延时加载。这里以属性的延迟加载为例,简单阐述Hibernate是如何使用动态代理的。假定有以下用户模型:publicclassUserimplementsjava.io.Serializable{
privateIntegerid;
privateStringname;
privateintage;
//省略getter和setter使用以下代码,通过Hibernate加载一条User信息:publicstaticvoidmain(String[]args)throwsSecurityException,
NoSuchFieldException,
IllegalArgumentException,
IllegalAccessException{
//从数据库载入ID为1的用户
Useru=(User)HibernateSessionFactory.getSession().load(User.class,1);
//打印类名称
System.out.println("ClassName:"+u.getClass().getName());
//打印父类名称
System.out.println("SuperClassName:"+u.getClass().getSuperclass().
getName());
//实现的所有接口
Class[]ins=u.getClass().getInterfaces();
for(Classcls:ins){
System.out.println("interface:"+cls.getName());
}
System.out.println(u.getName());
}以上代码在执行load(User.class.1)后,首先输出了User的类名、父类名以及User实现的接口,最后输出调用User的getName()方法,取得数据库中的数据。这段程序的输出结果如下(本例中使用的是Hibernate3.2.6,不同的Hibernate版本会有细节上的差异):ClassName:$xy.hibernate.User$
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 《六国论》课件的影视改编:2024年文化现象分析
- 《接触网施工》课件 4.8.2 无交叉线岔安装
- 2024年用友T6企业资源规划教程全解析
- 探索未知领域:2024年《生理学基础》教案展望
- 四年级语文下册作文教学计划
- 2024年春季学期:《长恨歌》的全新解读
- 《马钧传》教学创新策略:面向2024年
- 智能家具厂的账务处理实例-记账实操
- 2024年电子商务概论教案改革探讨
- 《高精度基准电压源》课件
- 大学美育(同济大学版)学习通超星期末考试答案章节答案2024年
- 劳动法律学习试题
- 过敏性休克完整版本
- 应急第一响应人理论考试试卷(含答案)
- DZ∕T 0213-2020 矿产地质勘查规范 石灰岩、水泥配料类(正式版)
- 2024年湖北省工业建筑集团有限公司招聘笔试参考题库含答案解析
- 软件工程师专业人物访谈
- 个人上学简历模板
- 冀教版八年级英语上册Unit 7 Lesson 37 What’s Your Hobby课件(共16张PPT)
- 小水电接入电力系统技术规定
- 第三章 玻璃分相与析晶
评论
0/150
提交评论