《分布式Java应用:基础与实践》样章、代码、纠错、补充

《分布式Java应用:基础与实践》一书中会存在一些错误的地方,以及一些尚未深入讲解的部分,在这篇文章里会提供纠错的信息以及补充的内容的文章的链接,关于书中错误的部分,还请各位海涵和帮助指正。

样章请从此下载:
http://bluedavy.me/book/booksample.pdf

随书的代码请从此处下载:
http://bluedavy.me/book/source.zip

《分布式Java应用:基础与实践》一书中会存在一些错误的地方,以及一些尚未深入讲解的部分,在这篇文章里会提供纠错的信息以及补充的内容的文章的链接,关于书中错误的部分,还请各位海涵和帮助指正。

样章请从此下载:
http://bluedavy.me/book/booksample.pdf

随书的代码请从此处下载:
http://bluedavy.me/book/source.zip

===============纠错===============

  • 第257页 — 感谢@偶系阿萌 的反馈
    “当节点较少时,有可能会出现这些机器节点是均匀分布的现象”
    应修改为:
    “当节点较少时,有可能会出现这些机器节点是不均匀分布的现象”

  • 第41页 — 感谢dongtalk的反馈
    “各厂商在实现JDK时通常会将符合Java语言规范的源代码编译为class文件的编译器”
    应修改为:
    “各厂商在实现JDK时通常会提供将符合Java语言规范的源代码编译为class文件的编译器”

  • 第46页 — 感谢yeshucheng的反馈
    图3.4中的“Booksrap ClassLoader”应为“BootStrap ClassLoader”

  • 第68页
    图3.13用来说明Sun hotspot中可用的GC,其中旧生代可用的GC的图有错误的地方,准确来说,在Sun Hotspot V 1.6.0中并行GC应该有两种:Parallel Mark Sweep和Parallel Compacting。

  • 第71页
    “只有经历过几次Minor GC仍然存活的对象,才放入旧生代中,这个在Minor GC中存活的次数在串行和ParNew方式时可通过-XX:MaxTenuringThreshold来设置,在Parallel Scavenge时则由Hotspot根据运行状况来决定。”
    这句话准确的应为:
    “只有经历过几次Minor GC仍然存活的对象,才放入旧生代中,这个在Minor GC中存活的最大次数在串行和ParNew方式时可通过-XX:MaxTenuringThreshold来设置,但并不代表对象一定会存活MaxTenuringThreshold次才会晋升到旧生代,串行和ParNew采用一个规则在每次Minor GC后计算可存活的次数,规则为累积每个age的对象所占的内存,一直计算到占用大小超过survivor space一半的age,如计算了所有的age,均未超过则以MaxTenuringThreshold为准,否则则以age为准,在Parallel Scavenge时默认情况下由Hotspot根据运行状况来决定。”。

  • 第73页
    “并行GC在基于SurvivorRatio值划分eden space和两块survivor space的方式上和串行GC一样。”
    修正为
    “默认情况下并行GC在基于SurvivorRatio值划分eden space和两块survivor space的方式上和串行GC一样,在开启-XX:+UseAdaptiveSizePolicy后则为每次Minor GC后动态计算eden、to的大小。”

  • 第73页
    “当在Eden Space上分配内存时Eden Space空间不足,JVM即触发Minor GC的执行,也可在程序中通过System.gc的方式(可通过在启动参数中增加-XX:+DisableExplicitGC来避免程序中调用System.gc触发GC)来触发。”
    修正为
    “当在Eden Space上分配内存时Eden Space空间不足,JVM即触发Minor GC的执行,当旧生代采用并行GC时,也可在程序中通过System.gc的方式(可通过在启动参数中增加-XX:+DisableExplicitGC来避免程序中调用System.gc触发GC)来触发。”

  • 第77页 — 感谢dongtalk的反馈
    “首先将代空间划分为并行线程个数的区域(regions)”
    应修改为
    “首先将旧生代空间划分为并行线程个数的区域(regions)”

  • 第77页
    并行应为并行Compacting,书中没有介绍Parallel Mark Sweep,在后续的blog中会进行完善;

  • 第78页
    这句话是错误的:“并行是server级别机器(非32位Windows)上默认采用的GC方式,也可通过-XX:+UseParallelGC或-XX:+UseParallelOldGC来强制指定。”
    修正为
    “并行是server级别机器(非32位Windows)上默认采用的GC方式,可通过-XX:+UseParallelGC来指定使用Parallel Mark Sweep,通过-XX:+UseParallelOldGC来指定使用Parallel Compacting。”;

  • 第80页
    “CMS GC触发的条件为旧生代已使用的空间达到设定的CMSInitiatingOccupancyFraction百分比,例如默认CMSInitiatingOccupancyFraction为68%,如旧生代空间为1 000MB,那么当旧生代已使用的空间达到680MB时,CMS GC即开始执行;”
    修正为
    ”CMS GC触发的条件为旧生代已使用的空间达到设定的CMSInitiatingOccupancyFraction百分比或持久代已使用的空间达到设定的CMSInitiatingPermOccupancyFraction百分比,例如在JDK 6.0中默认值为92,如旧生代空间为1000MB,那么当旧生代已使用的空间达到920MB时,CMS GC即开始执行;“

  • 第80页
    ”持久代的GC也可采用CMS方式,方式为设置以下参数:-XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled。“
    修正为
    ”持久代的GC也可采用CMS方式,方式为设置此参数:-XX:+CMSClassUnloadingEnabled。“;

  • 第87页
    表3.1中:Server模式下旧生代和持久代GC方式准确的说应为Parallel Mark Sweep;

  • 第87页
    表3.2中:”-XX:+UseParallelGC 并行回收GC 并行GC“准确的说应为:”-XX:+UseParallelGC 并行回收GC Parallel Mark Sweep GC“;

  • 第87页
    表3.2中:“-XX:+UseParallelOldGC 并行回收GC 并行GC”准确的说应为:“-XX:+UseParallelOldGC 并行回收GC 并行Compacting GC”;

  • 第99页 — 感谢liang xie的反馈
    “如果要分析jvm堆dumap文件”
    应为:
    “如果要分析jvm堆dump文件”

  • 第112页 — 感谢liang xie的反馈
    “JDK常用package中的常用类进行分析……….,对根据需求合理地选择类会有一定的帮助”产生了重复。

  • 第113页 — 感谢libai的反馈
    ”super调用的为AbstractList的默认构造器方法….ArrayList采用的是数组的方式来存放对象“
    这段话出现了重复。

  • 第183页 — 感谢liang xie的反馈
    “在没有安装pidstat或内核版本为2.6.20以后”
    应为
    “在没有安装pidstat或内核版本为2.6.20之前”

    ===============内容补充===============
    1、说说MaxTenuringThreshold这个参数
    2、Sun JDK OOM

  • 《分布式Java应用:基础与实践》序

    分布式Java应用需要开发人员掌握较多的知识点,通常分布式Java应用的场景还会对性能、可用性以及可伸缩有较高的要求,而这也就意味着开发人员需要掌握更多的知识点。我刚进淘宝的时候,曾经一直苦恼对于一个这样的分布式Java应用,我到底需要学习些什么。

    随着在淘宝工作的不断开展,我的眼前终于慢慢呈现了高性能、高可用以及可伸缩的Java应用所需知识点的全景,这张知识点的全景图现在已经演变成了本书的目录。当看到自己整理出的知识点的全景图时,很惊讶地发现其中有些知识点其实是我之前已经学习过的,但到了真正需要使用的时候有些是完全遗忘了,有些则是在使用时碰到了很多的问题,从这里我看到,当学习到的知识不去经过实践检验时,这些知识就不算真正属于自己。

    分布式Java应用需要开发人员掌握较多的知识点,通常分布式Java应用的场景还会对性能、可用性以及可伸缩有较高的要求,而这也就意味着开发人员需要掌握更多的知识点。我刚进淘宝的时候,曾经一直苦恼对于一个这样的分布式Java应用,我到底需要学习些什么。

    随着在淘宝工作的不断开展,我的眼前终于慢慢呈现了高性能、高可用以及可伸缩的Java应用所需知识点的全景,这张知识点的全景图现在已经演变成了本书的目录。当看到自己整理出的知识点的全景图时,很惊讶地发现其中有些知识点其实是我之前已经学习过的,但到了真正需要使用的时候有些是完全遗忘了,有些则是在使用时碰到了很多的问题,从这里我看到,当学习到的知识不去经过实践检验时,这些知识就不算真正属于自己。

    幸运的是,在淘宝我得到了分布式Java应用的绝佳实践机会,于是所学习到的网络通信、高性能、高并发、高可用以及可伸缩的一些知识,有机会在实践中得到验证。正是这样的机会,让我对这些知识点有了更深刻本质的理解,并能将其中的一些知识真正吸收,变为自己的经验,所以我一个很真切的体会就是:实践是最好的成长。

    经历了这段艰难的成长,自己也希望不要忘却在这个过程中的收获,胡适先生曾说:“发表是最好的记忆”(这句话也长期放在台湾技术作家侯捷老师的网站上),于是萌生了写这本书的想法,一方面想梳理自己通过实践所获得的成长,另一方面也希望与正在从事分布式Java应用的技术人员分享一些实践的心得,同时给将要或打算从事分布式Java应用的技术人员提供一些参考。

    从2008年11月确认要写这本书,到2010年5月完成这本书,历时一年半,过程可谓波折不断,前3个月的写作一帆风顺,顺利完成了第一章和第三章的撰写。

    到了2009年3月底后,由于投入到了《OSGi原理与最佳实践》一书的编写中,停顿了将近3个月的时间。

    2009年8月重新开始了这本书的撰写,在2009年10月下旬前按计划完成了第二章、第四章和第五章,但随后由于项目进入冲刺阶段、忙于校园招聘以及迁居等事情,再度停顿了本书的撰写。

    记得在刚开始写这本书的时候,周筠老师就告诉我,要坚持写,就算每天只写一点也是好的,千万不要停顿!确实如此,在停顿了两次后,就很难再找到继续写这本书的动力和激情了,得感谢徐定翔编辑在之后给我的鼓励和督促,终于在2010年3月我又开始了写作。待完成了第六章的编写后,由于剩下的第七章中的部分知识点和自己的工作联系不是非常紧密,导致了不断的拖延,这时周筠老师和徐定翔编辑给我的一个建议起到了关键作用,就是先放下第七章,先做之前完成的六章的定稿。

    回到自己熟悉的前6章,终于再次有了写作的动力,随着工作中对自己书中所涉及的知识点的不断实践,此时再回头看自己半年前甚至一年前写的初稿时,发现其中有不少的错误以及条理不够清晰的地方,于是进行了大刀阔斧的改动,实践所获得的积累此时起了巨大的推动作用,这6章定稿除了第三章以外的章节,完成得较为顺利。

    6章定稿提交后,由白爱萍编辑带领的编辑小组再次对书稿进行细致的“田间管理”,给出了非常多的修改建议,印象中几乎平均每页都至少有两到三处需要修改的地方,正是他们的认真和专业,使得定稿中很多语言上以及技术上的错误得以纠正,感谢武汉博文的编辑们。

    最后,在第三章定稿的修改过程中,得到了同事莫枢(http://rednaxelafx.javaeye.com)非常多的建议和帮助,在此非常感谢他的支持。

    全书在编写的过程中,初稿、定稿也提交给了一些业内专家帮忙评审,主要有:郑晖、霍炬、曹祺、刘力祥,他们给出的很多意见一方面纠正了书中一些技术上的错误,另一方面也让书的条理性更加顺畅,衷心感谢他们的辛勤付出。尤其郑晖老师,在承诺为本书写推荐序时,又花时间把全稿通读一遍,他的耐心和专业精神,让我感佩不已。

    书的撰写过程是如此漫长,每天晚上下班后、周末、假期,甚至过年期间,都成了写书的时间,感谢家人给我的包容、支持和理解,最要感谢的是我明年春节就将迎娶的准老婆:宗伟,感谢你忍受了我不断忘记买家里需要的各种东西,感谢你独立完成了新家的装修,更要感谢你允许我这么多的周末、假日都无法陪伴你,没有你的支持和鼓励,这本书是无法完成的。

    回顾整个编写过程,从开始编写,到提交完全部终稿,经历了15个月的时间,写作时间大概为11个月,经过这11个月对这些知识点的不断实践、回顾和总结,它们在我的脑海中刻下了深深的烙印,的确,发表是最好的记忆。

    林昊

    2010年5月20日晚于杭州家中

    《分布式Java应用:基础与实践》的封面和目录

    封面:

    目录请见全文。

    封面:

    目录如下:
    第1章 分布式Java应用 1
    1.1 基于消息方式实现系统间的通信 3
    1.1.1 基于Java自身技术实现消息方式的系统间通信 3
    1.1.2 基于开源框架实现消息方式的系统间通信 10
    1.2 基于远程调用方式实现系统间的通信 14
    1.2.1 基于Java自身技术实现远程调用方式的系统间通信 14
    1.2.2 基于开源框架实现远程调用方式的系统间通信 17
    第2章 大型分布式Java应用与SOA 23
    2.1 基于SCA实现SOA平台 26
    2.2 基于ESB实现SOA平台 29
    2.3 基于Tuscany实现SOA平台 30
    2.4 基于Mule实现SOA平台 34
    第3章 深入理解JVM 39
    3.1 Java代码的执行机制 40
    3.1.1 Java源码编译机制 41
    3.1.2 类加载机制 44
    3.1.3 类执行机制 49
    3.2 JVM内存管理 63
    3.2.1 内存空间 63
    3.2.2 内存分配 65
    3.2.3 内存回收 66
    3.2.4 JVM内存状况查看方法和分析工具 92
    3.3 JVM线程资源同步及交互机制 100
    3.3.1 线程资源同步机制 100
    3.3.2 线程交互机制 104
    3.3.3 线程状态及分析 105
    第4章 分布式应用与Sun JDK类库 111
    4.1 集合包 112
    4.1.1 ArrayList 113
    4.1.2 LinkedList 116
    4.1.3 Vector 117
    4.1.4 Stack 118
    4.1.5 HashSet 119
    4.1.6 TreeSet 120
    4.1.7 HashMap 120
    4.1.8 TreeMap 123
    4.1.9 性能测试 124
    4.1.10 小结 138
    4.2 并发包(java.util.concurrent) 138
    4.2.1 ConcurrentHashMap 139
    4.2.2 CopyOnWriteArrayList 145
    4.2.3 CopyOnWriteArraySet 149
    4.2.4 ArrayBlockingQueue 149
    4.2.5 AtomicInteger 151
    4.2.6 ThreadPoolExecutor 153
    4.2.7 Executors 157
    4.2.8 FutureTask 158
    4.2.9 Semaphore 161
    4.2.10 CountDownLatch 162
    4.2.11 CyclicBarrier 163
    4.2.12 ReentrantLock 164
    4.2.13 Condition 164
    4.2.14 ReentrantReadWriteLock 165
    4.3 序列化/反序列化 167
    4.3.1 序列化 167
    4.3.2 反序列化 170
    第5章 性能调优 173
    5.1 寻找性能瓶颈 175
    5.1.1 CPU消耗分析 175
    5.1.2 文件IO消耗分析 182
    5.1.3 网络IO消耗分析 186
    5.1.4 内存消耗分析 187
    5.1.5 程序执行慢原因分析 191
    5.2 调优 192
    5.2.1 JVM调优 192
    5.2.2 程序调优 202
    5.2.5 对于资源消耗不多,但程序执行慢的情况 214
    第6章 构建高可用的系统 227
    6.1 避免系统中出现单点 228
    6.1.1 负载均衡技术 228
    6.1.2 热备 236
    6.2 提高应用自身的可用性 238
    6.2.1 尽可能地避免故障 239
    6.2.2 及时发现故障 246
    6.2.3 及时处理故障 248
    6.2.4 访问量及数据量不断上涨的应对策略 249
    第7章 构建可伸缩的系统 251
    7.1 垂直伸缩 252
    7.1.1 支撑高访问量 252
    7.1.2 支撑大数据量 254
    7.1.3 提升计算能力 254
    7.2 水平伸缩 254
    7.2.1 支撑高访问量 254
    7.2.2 支撑大数据量 264
    7.2.3 提升计算能力 266

    Sun JDK V1.6.0 JVM GC

    本PPT目前版本仅为0.8,后续仍然会进一步完善,只介绍了在Sun Hotspot V 1.6.0中:
    1、内存结构;
    2、内存分代,如何控制代大小;
    3、可用的GC,每种GC对于参数的不同使用,例如SurvivorRatio、MaxTenuringThreshold等;每种GC不同的内存分配策略和回收策略,但不涉及具体算法是如何实现的;
    4、GC是怎么触发的,日志是什么含义;
    5、怎么使用上面的GC;
    6、GC Tuning,一个案例以及简单介绍了一些常见的GC调优的目标时的瓶颈、可采用的方法等;
    7、Sun JDK是如何实现GC的。

    本PPT目前版本仅为0.8(更新于2010-06-08),后续仍然会进一步完善,只介绍了在Sun Hotspot V 1.6.0中:
    1、内存结构;
    2、内存分代,如何控制代大小;
    3、可用的GC,每种GC对于参数的不同使用,例如SurvivorRatio、MaxTenuringThreshold等;每种GC不同的内存分配策略和回收策略,但不涉及具体算法是如何实现的;
    4、GC是怎么触发的,日志是什么含义;
    5、怎么使用上面的GC;
    6、GC Tuning,一个案例以及简单介绍了一些常见的GC调优的目标时的瓶颈、可采用的方法等;
    7、Sun JDK是如何实现GC的。

    code show: 网络访问超时优化

    在这篇blog中,我们将来探讨一个优化的案例,这个案例的背景为:在网络通信中,通常会有需求是从A发送了消息给B,然后同步或异步等待B的响应,但不可能无限期的等下去,因此会需要一个超时机制,通常在基于NIO这样的机制中,发送和接收会是异步方式,通常实现时会采用类似如下的方法,代码简单示例如下。

    在这篇blog中,我们将来探讨一个优化的案例,这个案例的背景为:在网络通信中,通常会有需求是从A发送了消息给B,然后同步或异步等待B的响应,但不可能无限期的等下去,因此会需要一个超时机制,通常在基于NIO这样的机制中,发送和接收会是异步方式,通常实现时会采用类似如下的方法,代码简单示例如下:

    invoke(){

    send(request);

    }

    onMessageReceived(){

    // 接收到响应后的处理

    }

    在不需要超时的情况下,这样就OK了,在有超时的情况下,则需要启动一个线程来扫描所有连接上(连接太多,所以不太可能每个连接就启一个线程来扫)的所有请求(每个请求的超时时间可能都不一样,有些是1s、有些是3s),这里就出现了一个问题,到底是多久扫描一次呢?简单的实现的话就可以是固定时间间隔扫描一次,这样扫描就显得有些傻了,因为其实很多请求根本就还没到超时时间,而随着连接数、请求数的增加,这里的效率就成问题了,高级点的话就演变为将最近超时的放到最顶端,类似Timer或DelayedQueue的实现,但这样的方式都存在一个问题,就是由于需要排序,所以在往queue里插入和删除对象的时候都需要加锁,这无疑会产生很大的影响,so无法接受,期望能做到的是:

    1、单线程扫描请求是否超时;

    2、在加入需要扫描的请求时无需加锁;

    3、超时扫描等到达了最近有超时的请求后才启动,否则沉默;

    4、当请求响应已回来后,可删除需要扫描的相应请求,避免在超时时间段内堆积太多扫描的请求,消耗内存。

    我们之前想到的一个方法为

    invoke(){

    addToScanQueue(request.getId(),request.getTimeout());

    send(request);

    }

    addToScanQueue{

    // 判断是否有对应request.getTimeout的queue,如果没有则创建,并往queue里塞入这次需要扫描的请求的id,超时时间,塞入后检查其超时时间和扫描线程的睡眠时间,如超时时间小于睡眠时间,则唤醒扫描线程

    }

    扫描线程{

    // 扫描所有的queue,并取出第一个对象,看看有没有超时,直到取到一个没超时的,比较所有queue中没超时的,看谁时间短,以短的为下次触发扫描的时间点

    }

    上面这个方法有效的借助了request.getTimeout种类不多的场景以及FIFO的特性,基本上可以做到插入无锁,而且扫描基本上可以等到有需要扫描的才扫描,但有个问题…就是已经有了响应的请求怎么从这里删除呢,一方面queue不好删,另一方面要删多数就要加锁了,如果转为用mark机制的话,由于大部分请求其实都不会超时的,这里会堆积很多的对象,消耗内存,纠结呀…

    有同学有兴趣尝试下改造上面的方式吗,既然是code show,感兴趣的同学可直接在回复中贴上代码,:)