最后更新时间 2009-04-10 更新人 dormando@rydia.net 这里收集了经常被问到的关于memcached的问题 " src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">集群架构方面的问题 " src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached是怎么工作的? Memcached的神奇来自两阶段哈希(two-stage hash)。Memcached就像一个巨大的、存储了很多<key,value>对的哈希表。通过key,可以存储或查询任意的数据。 客户端可以把数据存储在多台memcached上。当查询数据时,客户端首先参考节点列表计算出key的哈希值(阶段一哈希),进而选中一个节点; 客户端将请求发送给选中的节点,然后memcached节点通过一个内部的哈希算法(阶段二哈希),查找真正的数据(item)。 举个列子,假设有3个客户端1, 2, 3,3台memcached A, B, C: Client 1想把数据"barbaz"以key "foo"存储。Client 1首先参考节点列表(A, B, C),计算key "foo"的哈希值,假设memcached B被选中。接着,Client 1直接connect到memcached B,通过key "foo"把数据"barbaz"存储进去。Client 2使用与Client 1相同的客户端库(意味着阶段一的哈希算法相同),也拥有同样的memcached列表(A, B, C)。 于是,经过相同的哈希计算(阶段一),Client 2计算出key "foo"在memcached B上,然后它直接请求memcached B,得到数据"barbaz"。 各种客户端在memcached中数据的存储形式是不同的(perl Storable, php serialize, java hibernate, JSON等)。一些客户端实现的哈希算法也不一样。但是,memcached服务器端的行为总是一致的。 最后,从实现的角度看,memcached是一个非阻塞的、基于事件的服务器程序。这种架构可以很好地解决C10K problem ,并具有极佳的可扩展性。 可以参考A Story of Caching ,这篇文章简单解释了客户端与memcached是如何交互的。 memcached最大的优势是什么? 请仔细阅读上面的问题(即memcached是如何工作的)。Memcached最大的好处就是它带来了极佳的水平可扩展性,特别是在一个巨大的系 统中。由于客户端自己做了一次哈希,那么我们很容易增加大量memcached到集群中。memcached之间没有相互通信,因此不会增加 memcached的负载;没有多播协议,不会网络通信量爆炸(implode)。memcached的集群很好用。内存不够了?增加几台 memcached吧;CPU不够用了?再增加几台吧;有多余的内存?在增加几台吧,不要浪费了。 基于memcached的基本原则,可以相当轻松地构建出不同类型的缓存架构。除了这篇FAQ,在其他地方很容易找到详细资料的。 看看下面的几个问题吧,它们在memcached、服务器的local cache和MySQL的query cache之间做了比较。这几个问题会让您有更全面的认识。 " src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached和MySQL的query cache相比,有什么优缺点? 把memcached引入应用中,还是需要不少工作量的。MySQL有个使用方便的query cache,可以自动地缓存SQL查询的结果,被缓存的SQL查询可以被反复地快速执行。Memcached与之相比,怎么样呢?MySQL的query cache是集中式的,连接到该query cache的MySQL服务器都会受益。 - 当您修改表时,MySQL的query cache会立刻被刷新(flush)。存储一个memcached item只需要很少的时间,但是当写操作很频繁时,MySQL的query cache会经常让所有缓存数据都失效。
- 在多核CPU上,MySQL的query cache会遇到扩展问题(scalability issues)。在多核CPU上,query cache会增加一个全局锁(global lock), 由于需要刷新更多的缓存数据,速度会变得更慢。
- 在MySQL的query cache中,我们是不能存储任意的数据的(只能是SQL查询结果)。而利用memcached,我们可以搭建出各种高效的缓存。比如,可以执行多个独立 的查询,构建出一个用户对象(user object),然后将用户对象缓存到memcached中。而query cache是SQL语句级别的,不可能做到这一点。在小的网站中,query cache会有所帮助,但随着网站规模的增加,query cache的弊将大于利。
- query cache能够利用的内存容量受到MySQL服务器空闲内存空间的限制。给数据库服务器增加更多的内存来缓存数据,固然是很好的。但是,有了memcached,只要您有空闲的内存,都可以用来增加memcached集群的规模,然后您就可以缓存更多的数据。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached和服务器的local cache(比如PHP的APC、mmap文件等)相比,有什么优缺点? 首先,local cache有许多与上面(query cache)相同的问题。local cache能够利用的内存容量受到(单台)服务器空闲内存空间的限制。不过,local cache有一点比memcached和query cache都要好,那就是它不但可以存储任意的数据,而且没有网络存取的延迟。 - local cache的数据查询更快。考虑把highly common的数据放在local cache中吧。如果每个页面都需要加载一些数量较少的数据,考虑把它们放在local cached吧。
- local cache缺少集体失效(group invalidation)的特性。在memcached集群中,删除或更新一个key会让所有的观察者觉察到。但是在local cache中, 我们只能通知所有的服务器刷新cache(很慢,不具扩展性),或者仅仅依赖缓存超时失效机制。
- local cache面临着严重的内存限制,这一点上面已经提到。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached的cache机制是怎样的? Memcached主要的cache机制是LRU(最近最少用)算法+超时失效。当您存数据到memcached中,可以指定该数据在缓存中可以呆 多久Which is forever, or some time in the future。如果memcached的内存不够用了,过期的slabs会优先被替换,接着就轮到最老的未被使用的slabs。 " src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached如何实现冗余机制? 不 实现!我们对这个问题感到很惊讶。Memcached应该是应用的缓存层。它的设计本身就不带有任何冗余机制。如果一个memcached节点失去了所有 数据,您应该可以从数据源(比如数据库)再次获取到数据。您应该特别注意,您的应用应该可以容忍节点的失效。不要写一些糟糕的查询代码,寄希望于 memcached来保证一切!如果您担心节点失效会大大加重数据库的负担,那么您可以采取一些办法。比如您可以增加更多的节点(来减少丢失一个节点的影 响),热备节点(在其他节点down了的时候接管IP),等等。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached如何处理容错的? 不处理!:) 在memcached节点失效的情况下,集群没有必要做任何容错处理。如果发生了节点失效,应对的措施完全取决于用户。节点失效时,下面列出几种方案供您选择: - 忽略它! 在失效节点被恢复或替换之前,还有很多其他节点可以应对节点失效带来的影响。
- 把失效的节点从节点列表中移除。做这个操作千万要小心!在默认情况下(余数式哈希算法),客户端添加或移除节点,会导致所有的缓存数据不可用!因为哈希参照的节点列表变化了,大部分key会因为哈希值的改变而被映射到(与原来)不同的节点上。
- 启动热备节点,接管失效节点所占用的IP。这样可以防止哈希紊乱(hashing chaos)。
- 如果希望添加和移除节点,而不影响原先的哈希结果,可以使用一致性哈希算法(consistent hashing)。您可以百度一下一致性哈希算法。支持一致性哈希的客户端已经很成熟,而且被广泛使用。去尝试一下吧!
- 两次哈希(reshing)。当客户端存取数据时,如果发现一个节点down了,就再做一次哈希(哈希算法与前一次不同),重新选择另一个节点 (需要注意的时,客户端并没有把down的节点从节点列表中移除,下次还是有可能先哈希到它)。如果某个节点时好时坏,两次哈希的方法就有风险了,好的节 点和坏的节点上都可能存在脏数据(stale data)。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">如何将memcached中item批量导入导出? 您不应该这样做!Memcached是一个非阻塞的服务器。任何可能导致memcached暂停或瞬时拒绝服务的操作都应该值得深思熟虑。向 memcached中批量导入数据往往不是您真正想要的!想象看,如果缓存数据在导出导入之间发生了变化,您就需要处理脏数据了;如果缓存数据在导出导入 之间过期了,您又怎么处理这些数据呢? 因此,批量导出导入数据并不像您想象中的那么有用。不过在一个场景倒是很有用。如果您有大量的从不变化 的数据,并且希望缓存很快热(warm)起来,批量导入缓存数据是很有帮助的。虽然这个场景并不典型,但却经常发生,因此我们会考虑在将来实现批量导出导入的功能。 Steven Grimm,一如既往地,,在邮件列表中给出了另一个很好的例子:http://lists.danga.com/pipermail/memcached/2007-July/004802.html 。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">但是我确实需要把memcached中的item批量导出导入,怎么办?? 好吧好吧。如果您需要批量导出导入,最可能的原因一般是重新生成缓存数据需要消耗很长的时间,或者数据库坏了让您饱受痛苦。 如果一个memcached节点down了让您很痛苦,那么您还会陷入其他很多麻烦。您的系统太脆弱了。您需要做一些优化工作。比如处理"惊群"问 题(比如 memcached节点都失效了,反复的查询让您的数据库不堪重负...这个问题在FAQ的其他提到过),或者优化不好的查询。记住,Memcached 并不是您逃避优化查询的借口。 如果您的麻烦仅仅是重新生成缓存数据需要消耗很长时间(15秒到超过5分钟),您可以考虑重新使用数据库。这里给出一些提示: - 使用MogileFS(或者CouchDB等类似的软件)在存储item。把item计算出来并dump到磁盘上。MogileFS可以很方便地 覆写item,并提供快速地访问。.您甚至可以把MogileFS中的item缓存在memcached中,这样可以加快读取速度。 MogileFS+Memcached的组合可以加快缓存不命中时的响应速度,提高网站的可用性。
- 重新使用MySQL。MySQL的 InnoDB主键查询的速度非常快。如果大部分缓存数据都可以放到VARCHAR字段中,那么主键查询的性能将更好。从memcached中按key查询 几乎等价于MySQL的主键查询:将key 哈希到64-bit的整数,然后将数据存储到MySQL中。您可以把原始(不做哈希)的key存储都普通的字段中,然后建立二级索引来加快查 询...key被动地失效,批量删除失效的key,等等。
上面的方法都可以引入memcached,在重启memcached的时候仍然提供很好的性能。由于您不需要当心"hot"的item被 memcached LRU算法突然淘汰,用户再也不用花几分钟来等待重新生成缓存数据(当缓存数据突然从内存中消失时),因此上面的方法可以全面提高性能。 关于这些方法的细节,详见博客:http://dormando.livejournal.com/495593.html 。 " src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached是如何做身份验证的? 没有身份认证机制!memcached是运行在应用下层的软件(身份验证应该是应用上层的职责)。memcached的客户端和服务器端之所以是轻量级的,部分原因就是完全没有实现身份验证机制。这样,memcached可以很快地创建新连接,服务器端也无需任何配置。
如果您希望限制访问,您可以使用防火墙,或者让memcached监听unix domain socket。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached的多线程是什么?如何使用它们? 线 程就是定律(threads rule)!在Steven Grimm和Facebook的努力下,memcached 1.2及更高版本拥有了多线程模式。多线程模式允许memcached能够充分利用多个CPU,并在CPU之间共享所有的缓存数据。memcached使 用一种简单的锁机制来保证数据更新操作的互斥。相比在同一个物理机器上运行多个memcached实例,这种方式能够更有效地处理multi gets。
如果您的系统负载并不重,也许您不需要启用多线程工作模式。如果您在运行一个拥有大规模硬件的、庞大的网站,您将会看到多线程的好处。
更多信息请参见:http://code.sixapart.com/svn/memcached/trunk/server/doc/threads.txt 。
简 单地总结一下:命令解析(memcached在这里花了大部分时间)可以运行在多线程模式下。memcached内部对数据的操作是基于很多全局锁的(因 此这部分工作不是多线程的)。未来对多线程模式的改进,将移除大量的全局锁,提高memcached在负载极高的场景下的性能。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached能接受的key的最大长度是多少? key 的最大长度是250个字符。需要注意的是,250是memcached服务器端内部的限制,如果您使用的客户端支持"key的前缀"或类似特性,那么 key(前缀+原始key)的最大长度是可以超过250个字符的。我们推荐使用使用较短的key,因为可以节省内存和带宽。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached对item的过期时间有什么限制? 过期时间最大可以达到30天。memcached把传入的过期时间(时间段)解释成时间点后,一旦到了这个时间点,memcached就把item置为失效状态。这是一个简单但obscure的机制。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached最大能存储多大的单个item? 1MB。如果你的数据大于1MB,可以考虑在客户端压缩或拆分到多个key中。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">为什么单个item的大小被限制在1M byte之内? 啊...这是一个大家经常问的问题!
简单的回答:因为内存分配器的算法就是这样的。
详 细的回答:Memcached的内存存储引擎(引擎将来可插拔...),使用slabs来管理内存。内存被分成大小不等的slabs chunks(先分成大小相等的slabs,然后每个slab被分成大小相等chunks,不同slab的chunk大小是不相等的)。chunk的大小 依次从一个最小数开始,按某个因子增长,直到达到最大的可能值。
如果最小值为400B,最大值是1MB,因子是1.20,各个slab的chunk的大小依次是:slab1 - 400B slab2 - 480B slab3 - 576B ...
slab中chunk越大,它和前面的slab之间的间隙就越大。因此,最大值越大,内存利用率越低。Memcached必须为每个slab预先分配内存,因此如果设置了较小的因子和较大的最大值,会需要更多的内存。
还有其他原因使得您不要这样向memcached中存取很大的数据...不要尝试把巨大的网页放到mencached中。把这样大的数据结构load和unpack到内存中需要花费很长的时间,从而导致您的网站性能反而不好。
如果您确实需要存储大于1MB的数据,你可以修改slabs.c:POWER_BLOCK的值,然后重新编译memcached;或者使用低效的malloc/free。其他的建议包括数据库、MogileFS等。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">我可以在不同的memcached节点上使用大小不等的缓存空间吗?这么做之后,memcached能够更有效地使用内存吗? Memcache 客户端仅根据哈希算法来决定将某个key存储在哪个节点上,而不考虑节点的内存大小。因此,您可以在不同的节点上使用大小不等的缓存。但是一般都是这样做 的:拥有较多内存的节点上可以运行多个memcached实例,每个实例使用的内存跟其他节点上的实例相同。
" src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">什么是二进制协议,我该关注吗? 关于二进制最好的信息当然是二进制协议规范:http://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol 。
二进制协议尝试为端提供一个更有效的、可靠的协议,减少客户端/服务器端因处理协议而产生的CPU时间。 根据Facebook的测试,解析ASCII协议是memcached中消耗CPU时间最多的环节。所以,我们为什么不改进ASCII协议呢? 在这个邮件列表的thread中可以找到一些旧的信息:http://lists.danga.com/pipermail/memcached/2007-July/004636.html 。 " src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached的内存分配器是如何工作的?为什么不适用malloc/free!?为何要使用slabs? 实 际上,这是一个编译时选项。默认会使用内部的slab分配器。您确实确实应该使用内建的slab分配器。最早的时候,memcached只使用 malloc/free来管理内存。然而,这种方式不能与OS的内存管理以前很好地工作。反复地malloc/free造成了内存碎片,OS最终花费大量 的时间去查找连续的内存块来满足malloc的请求,而不是运行memcached进程。如果您不同意,当然可以使用malloc!只是不要在邮件列表中 抱怨啊:)
slab分配器就是为了解决这个问题而生的。内存被分配并划分成chunks,一直被重复使用。因为内存被划分成大小不等的 slabs,如果item的大小与被选择存放它的slab不是很合适的话,就会浪费一些内存。Steven Grimm正在这方面已经做出了有效的改进。
邮件列表中有一些关于slab的改进(power of n 还是 power of 2)和权衡方案:http://lists.danga.com/pipermail/memcached/2006-May/002163.html http://lists.danga.com/pipermail/memcached/2007-March/003753.html 。
如果您想使用malloc/free,看看它们工作地怎么样,您可以在构建过程中定义USE_SYSTEM_MALLOC。这个特性没有经过很好的测试,所以太不可能得到开发者的支持。 更多信息:http://code.sixapart.com/svn/memcached/trunk/server/doc/memory_management.txt 。 " src="/CuteSoft_Client/CuteEditor/Images/anchor.gif">memcached是原子的吗? 当然!好吧,让我们来明确一下: 所有的被发送到memcached的单个命令是完全原子的。如果您针对同一份数据同时发送了一个set命令和一个get命令,它们不会影响对方。它们将被串行化、先后执行。即使在多线程模式,所有的命令都是原子的,除非程序有bug:) 命令序列不是原子的。如果您通过get命令获取了一个item,修改了它,然后想把它set回memcached,我们不保证这个item没有被其他进程(process,未必是操作系统中的进程)操作过。在并发的情况下,您也可能覆写了一个被其他进程set的item。
memcached 1.2.5以及更高版本,提供了gets和cas命令,它们可以解决上面的问题。如果您使用gets命令查询某个key的item,memcached会 给您返回该item当前值的唯一标识。如果您覆写了这个item并想把它写回到memcached中,您可以通过cas命令把那个唯一标识一起发送给 memcached。如果该item存放在memcached中的唯一标识与您提供的一致,您的写操作将会成功。如果另一个进程在这期间也修改了这个 item,那么该item存放在memcached中的唯一标识将会改变,您的写操作就会失败。
通常,基于memcached中item的值来修改item,是一件棘手的事情。除非您很清楚自己在做什么,否则请不要做这样的事情。
SQL SERVER的排序规则平时使用不是很多,也许不少初学者还比较陌生,但有一个错误大家应是经常碰到: SQL SERVER数据库,在跨库多表连接查询时,若两数据库默认字符集不同,系统就会返回这样的错误: “无法解决 equal to 操作的排序规则冲突。” 一.错误分析: 这个错误是因为排序规则不一致造成的,我们做个测试,比如: create table #t1( name varchar(20) collate Albanian_CI_AI_WS, value int)
create table #t2( name varchar(20) collate Chinese_PRC_CI_AI_WS, value int )
表建好后,执行连接查询:
select * from #t1 A inner join #t2 B on A.name=B.name
这样,错误就出现了:
服务器: 消息 446,级别 16,状态 9,行 1 无法解决 equal to 操作的排序规则冲突。 要排除这个错误,最简单方法是,表连接时指定它的排序规则,这样错误就不再出现了。语句这样写:
select * from #t1 A inner join #t2 B on A.name=B.name collate Chinese_PRC_CI_AI_WS
二.排序规则简介:
什么叫排序规则呢?MS是这样描述的:"在 Microsoft SQL Server 2000 中,字符串的物理存储由排序规则控制。排序规则指定表示每个字符的位模式以及存储和比较字符所使用的规则。 在查询分析器内执行下面语句,可以得到SQL SERVER支持的所有排序规则。
select * from ::fn_helpcollations()
排序规则名称由两部份构成,前半部份是指本排序规则所支持的字符集。 如: Chinese_PRC_CS_AI_WS 前半部份:指UNICODE字符集,Chinese_PRC_指针对大陆简体字UNICODE的排序规则。 排序规则的后半部份即后缀 含义: _BIN 二进制排序 _CI(CS) 是否区分大小写,CI不区分,CS区分 _AI(AS) 是否区分重音,AI不区分,AS区分 _KI(KS) 是否区分假名类型,KI不区分,KS区分 _WI(WS) 是否区分宽度 WI不区分,WS区分
区分大小写:如果想让比较将大写字母和小写字母视为不等,请选择该选项。 区分重音:如果想让比较将重音和非重音字母视为不等,请选择该选项。如果选择该选项,比较还将重音不同的字母视为不等。 区分假名:如果想让比较将片假名和平假名日语音节视为不等,请选择该选项。 区分宽度:如果想让比较将半角字符和全角字符视为不等,请选择该选项 三.排序规则的应用: SQL SERVER提供了大量的WINDOWS和SQLSERVER专用的排序规则,但它的应用往往被开发人员所忽略。其实它在实践中大有用处。
例1:让表NAME列的内容按拼音排序:
create table #t(id int,name varchar(20)) insert #t select 1,'中' union all select 2,'国' union all select 3,'人' union all select 4,'阿'
select * from #t order by name collate Chinese_PRC_CS_AS_KS_WS drop table #t /*结果: id name ----------- -------------------- 4 阿 2 国 3 人 1 中 */
例2:让表NAME列的内容按姓氏笔划排序:
create table #t(id int,name varchar(20))
insert #t select 1,'三' union all select 2,'乙' union all select 3,'二' union all select 4,'一' union all select 5,'十' select * from #t order by name collate Chinese_PRC_Stroke_CS_AS_KS_WS drop table #t /*结果: id name ----------- -------------------- 4 一 2 乙 3 二 5 十 1 三 */
四.在实践中排序规则应用的扩展 SQL SERVER汉字排序规则可以按拼音、笔划等排序,那么我们如何利用这种功能来处理汉字的一些难题呢?我现在举个例子:
用排序规则的特性计算汉字笔划
要计算汉字笔划,我们得先做准备工作,我们知道,WINDOWS多国汉字,UNICODE目前 收录汉字共20902个。简体GBK码汉字UNICODE值从19968开始。 首先,我们先用SQLSERVER方法得到所有汉字,不用字典,我们简单利用SQL语句就可以得到:
select top 20902 code=identity(int,19968,1) into #t from syscolumns a,syscolumns b
再用以下语句,我们就得到所有汉字,它是按UNICODE值排序的:
select code,nchar(code) as CNWord from #t
然后,我们用SELECT语句,让它按笔划排序。
select code,nchar(code) as CNWord from #t order by nchar(code) collate Chinese_PRC_Stroke_CS_AS_KS_WS,code
结果: code CNWord ----------- ------ 19968 一 20008 丨 20022 丶 20031 丿 20032 乀 20033 乁 20057 乙 20058 乚 20059 乛 20101 亅 19969 丁 ..........
从上面的结果,我们可以清楚的看到,一笔的汉字,code是从19968到20101,从小到大排,但到了二笔汉字的第一个字“丁”,CODE为 19969,就不按顺序而重新开始了。有了这结果,我们就可以轻松的用SQL语句得到每种笔划汉字归类的第一个或最后一个汉字。 下面用语句得到最后一个汉字:
create table #t1(id int identity,code int,cnword nvarchar(2))
insert #t1(code,cnword) select code,nchar(code) as CNWord from #t order by nchar(code) collate Chinese_PRC_Stroke_CS_AS_KS_WS,code
select A.cnword from #t1 A left join #t1 B on A.id=B.id-1 and A.code<B.code where B.code is null order by A.id
得到36个汉字,每个汉字都是每种笔划数按Chinese_PRC_Stroke_CS_AS_KS_WS排序规则排序后的 最后一个汉字:
亅阝马风龙齐龟齿鸩龀龛龂龆龈龊龍龠龎龐龑龡龢龝齹龣龥齈龞麷鸞麣龖龗齾齉龘
上面可以看出:“亅”是所有一笔汉字排序后的最后一个字,“阝”是所有二笔汉字排序后的最后一个字......等等。 但同时也发现,从第33个汉字“龗(33笔)”后面的笔划有些乱,不正确。但没关系,比“龗”笔划多的只有四个汉字,我们手工加上:齾35笔,齉36笔,靐39笔,龘64笔 建汉字笔划表(TAB_HZBH): create table tab_hzbh(id int identity,cnword nchar(1)) --先插入前33个汉字 insert tab_hzbh select top 33 A.cnword from #t1 A left join #t1 B on A.id=B.id-1 and A.code<B.code where B.code is null order by A.id --再加最后四个汉字 set identity_insert tab_hzbh on go insert tab_hzbh(id,cnword) select 35,N'齾' union all select 36,N'齉' union all select 39,N'靐' union all select 64,N'龘' go set identity_insert tab_hzbh off go
到此为止,我们可以得到结果了,比如我们想得到汉字“国”的笔划:
declare @a nchar(1) set @a='国' select top 1 id from tab_hzbh where cnword>=@a collate Chinese_PRC_Stroke_CS_AS_KS_WS order by id
id ----------- 8 (结果:汉字“国”笔划数为8)
上面所有准备过程,只是为了写下面这个函数,这个函数撇开上面建的所有临时表和固定表,为了通用和代码转移方便,把表tab_hzbh的内容写在语句内,然后计算用户输入一串汉字的总笔划:
create function fun_getbh(@str nvarchar(4000)) returns int as begin declare @word nchar(1),@n int set @n=0 while len(@str)>0 begin set @word=left(@str,1) --如果非汉字,笔划当0计 set @n=@n+(case when unicode(@word) between 19968 and 19968+20901 then (select top 1 id from ( select 1 as id,N'亅' as word union all select 2,N'阝' union all select 3,N'马' union all select 4,N'风' union all select 5,N'龙' union all select 6,N'齐' union all select 7,N'龟' union all select 8,N'齿' union all select 9,N'鸩' union all select 10,N'龀' union all select 11,N'龛' union all select 12,N'龂' union all select 13,N'龆' union all select 14,N'龈' union all select 15,N'龊' union all select 16,N'龍' union all select 17,N'龠' union all select 18,N'龎' union all select 19,N'龐' union all select 20,N'龑' union all select 21,N'龡' union all select 22,N'龢' union all select 23,N'龝' union all select 24,N'齹' union all select 25,N'龣' union all select 26,N'龥' union all select 27,N'齈' union all select 28,N'龞' union all select 29,N'麷' union all select 30,N'鸞' union all select 31,N'麣' union all select 32,N'龖' union all select 33,N'龗' union all select 35,N'齾' union all select 36,N'齉' union all select 39,N'靐' union all select 64,N'龘' ) T where word>=@word collate Chinese_PRC_Stroke_CS_AS_KS_WS order by id ASC) else 0 end) set @str=right(@str,len(@str)-1) end return @n end
--函数调用实例: select dbo.fun_getbh('中华人民共和国'),dbo.fun_getbh('中華人民共和國') 执行结果:笔划总数分别为39和46,简繁体都行。
当然,你也可以把上面“UNION ALL”内的汉字和笔划改存在固定表内,在汉字列建CLUSTERED INDEX,列排序规则设定为: Chinese_PRC_Stroke_CS_AS_KS_WS 这样速度更快。如果你用的是BIG5码的操作系统,你得另外生成汉字,方法一样。但有一点要记住:这些汉字是通过SQL语句SELECT出来的,不是手工输入的,更不是查字典得来的,因为新华字典毕竟不同于UNICODE字符集,查字典的结果会不正确。
用排序规则的特性得到汉字拼音首字母
用得到笔划总数相同的方法,我们也可以写出求汉字拼音首字母的函数。如下:
create function fun_getPY(@str nvarchar(4000)) returns nvarchar(4000) as begin declare @word nchar(1),@PY nvarchar(4000) set @PY='' while len(@str)>0 begin set @word=left(@str,1) --如果非汉字字符,返回原字符 set @PY=@PY+(case when unicode(@word) between 19968 and 19968+20901 then (select top 1 PY from ( select 'A' as PY,N'驁' as word union all select 'B',N'簿' union all select 'C',N'錯' union all select 'D',N'鵽' union all select 'E',N'樲' union all select 'F',N'鰒' union all select 'G',N'腂' union all select 'H',N'夻' union all select 'J',N'攈' union all select 'K',N'穒' union all select 'L',N'鱳' union all select 'M',N'旀' union all select 'N',N'桛' union all select 'O',N'漚' union all select 'P',N'曝' union all select 'Q',N'囕' union all select 'R',N'鶸' union all select 'S',N'蜶' union all select 'T',N'籜' union all select 'W',N'鶩' union all select 'X',N'鑂' union all select 'Y',N'韻' union all select 'Z',N'咗' ) T where word>=@word collate Chinese_PRC_CS_AS_KS_WS order by PY ASC) else @word end) set @str=right(@str,len(@str)-1) end return @PY end
--函数调用实例: select dbo.fun_getPY('中华人民共和国'),dbo.fun_getPY('中華人民共和國') 结果都为:ZHRMGHG
你若有兴趣,也可用相同的方法,扩展为得到汉字全拼的函数,甚至还可以得到全拼的读音声调,不过全拼分类大多了。得到全拼最好是用对照表,两万多汉字搜索速度很快,用对照表还可以充分利用表的索引。 排序规则还有很多其它的巧妙用法。欢迎大家共同探讨。
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html The JavaTM Virtual Machine Specification Second Edition Tim Lindholm Frank Yellin
- Preface
- 1 Introduction
- 1.1 A Bit of History
- 1.2 The Java Virtual Machine
- 1.3 Summary of Chapters
- 1.4 Notation
- 2 Java Programming Language Concepts
- 2.1 Unicode
- 2.2 Identifiers
- 2.3 Literals
- 2.4 Types and Values
- 2.4.1 Primitive Types and Values 2.4.2 Operators on Integral Values
- 2.4.3 Floating-Point Types, Value Sets, and Values
- 2.4.4 Operators on Floating-Point Values
- 2.4.5 Operators on
boolean Values
- 2.4.6 Reference Types, Objects, and Reference Values
- 2.4.7 The Class
Object
- 2.4.8 The Class
String
- 2.4.9 Operators on Objects
- 2.5 Variables
- 2.5.1 Initial Values of Variables
- 2.5.2 Variables Have Types, Objects Have Classes
- 2.6 Conversions and Promotions
- 2.6.1 Identity Conversions
- 2.6.2 Widening Primitive Conversions
- 2.6.3 Narrowing Primitive Conversions
- 2.6.4 Widening Reference Conversions
- 2.6.5 Narrowing Reference Conversions
- 2.6.6 Value Set Conversion
- 2.6.7 Assignment Conversion
- 2.6.8 Method Invocation Conversion
- 2.6.9 Casting Conversion
- 2.6.10 Numeric Promotion
- 2.7 Names and Packages
- 2.7.1 Simple Names and Qualified Names
- 2.7.2 Packages
- 2.7.3 Members
- 2.7.4 Qualified Names and Access Control
- 2.7.5 Fully Qualified Names
- 2.8 Classes
- 2.8.1 Class Names
- 2.8.2 Class Modifiers
- 2.8.3 Superclasses and Subclasses
- 2.8.4 The Class Members
- 2.9 Fields
- 2.9.1 Field Modifiers
- 2.9.2 Initialization of Fields
- 2.10 Methods
- 2.10.1 Formal Parameters
- 2.10.2 Method Signature
- 2.10.3 Method Modifiers
- 2.11 Static Initializers
- 2.12 Constructors
- 2.12.1 Constructor Modifiers
2.13 Interfaces
- 2.13.1 Interface Modifiers
- 2.13.2 Superinterfaces
- 2.13.3 Interface Members
- 2.13.4 Overriding, Inheritance, and Overloading in Interfaces
- 2.14 Nested Classes and Interfaces
- 2.15 Arrays
- 2.15.1 Array Types
- 2.15.2 Array Variables
- 2.15.3 Array Creation
- 2.15.4 Array Access
- 2.16 Exceptions
- 2.16.1 The Causes of Exceptions
- 2.16.2 Handling an Exception
- 2.16.3 The Exception Hierarchy
- 2.16.4 The Classes Exception and RuntimeException
- 2.17 Execution
- 2.17.1 Virtual Machine Start-up
- 2.17.2 Loading
- 2.17.3 Linking: Verification, Preparation, and Resolution
- 2.17.4 Initialization
- 2.17.5 Detailed Initialization Procedure
- 2.17.6 Creation of New Class Instances
- 2.17.7 Finalization of Class Instances
- 2.17.8 Unloading of Classes and Interfaces
- 2.17.9 Virtual Machine Exit
- 2.18 FP-strict Expressions
- 2.19 Threads
- 3 The Structure of the Java Virtual Machine
- 3.1 The
class File Format
- 3.2 Data Types
- 3.3 Primitive Types and Values
- 3.3.1 Integral Types and Values
- 3.3.2 Floating-Point Types, Value Sets, and Values
- 3.3.3 The
returnAddress Type and Values
- 3.3.4 The
boolean Type 3.4 Reference Types and Values
- 3.5 Runtime Data Areas
- 3.5.1 The
pc Register
- 3.5.2 Java Virtual Machine Stacks
- 3.5.3 Heap
- 3.5.4 Method Area
- 3.5.5 Runtime Constant Pool
- 3.5.6 Native Method Stacks
3.6 Frames
- 3.6.1 Local Variables
- 3.6.2 Operand Stacks
- 3.6.3 Dynamic Linking
- 3.6.4 Normal Method Invocation Completion
- 3.6.5 Abrupt Method Invocation Completion
- 3.6.6 Additional Information
3.7 Representation of Objects
- 3.8 Floating-Point Arithmetic
- 3.8.1 Java Virtual Machine Floating-Point Arithmetic and IEEE 754
- 3.8.2 Floating-Point Modes
- 3.8.3 Value Set Conversion
3.9 Specially Named Initialization Methods
- 3.10 Exceptions
- 3.11 Instruction Set Summary
- 3.11.1 Types and the Java Virtual Machine
- 3.11.2 Load and Store Instructions
- 3.11.3 Arithmetic Instructions
- 3.11.4 Type Conversion Instructions
- 3.11.5 Object Creation and Manipulation
- 3.11.6 Operand Stack Management Instructions
- 3.11.7 Control Transfer Instructions
- 3.11.8 Method Invocation and Return Instructions
- 3.11.9 Throwing Exceptions
- 3.11.10 Implementing finally
- 3.11.11 Synchronization
3.12 Class Libraries
- 3.13 Public Design, Private Implementation
- 4 The
class File Format
- 4.1 The
ClassFile Structure
- 4.2 The Internal Form of Fully Qualified Class and Interface Names
- 4.3 Descriptors
- 4.3.1 Grammar Notation
- 4.3.2 Field Descriptors
- 4.3.3 Method Descriptors
4.4 The Constant Pool
- 4.4.1 The
CONSTANT_Class_info Structure
- 4.4.2 The
CONSTANT_Fieldref_info , CONSTANT_Methodref_info , and CONSTANT_InterfaceMethodref_info Structures
- 4.4.3 The
CONSTANT_String_info Structure
- 4.4.4 The
CONSTANT_Integer_info and CONSTANT_Float_info Structures
- 4.4.5 The
CONSTANT_Long_info and CONSTANT_Double_info Structures
- 4.4.6 The
CONSTANT_NameAndType_info Structure
- 4.4.7 The
CONSTANT_Utf8_info Structure 4.5 Fields
- 4.6 Methods
- 4.7 Attributes
- 4.7.1 Defining and Naming New Attributes
- 4.7.2 The
ConstantValue Attribute
- 4.7.3 The
Code Attribute
- 4.7.4 The
Exceptions Attribute
- 4.7.5 The
InnerClasses Attribute
- 4.7.6 The
Synthetic Attribute
- 4.7.7 The
SourceFile Attribute
- 4.7.8 The
LineNumberTable Attribute
- 4.7.9 The
LocalVariableTable Attribute
- 4.7.10 The
Deprecated Attribute 4.8 Constraints on Java Virtual Machine Code
- 4.8.1 Static Constraints
- 4.8.2 Structural Constraints
4.9 Verification of class Files
- 4.9.1 The Verification Process
- 4.9.2 The Bytecode Verifier
- 4.9.3 Values of Types
long and double
- 4.9.4 Instance Initialization Methods and Newly Created Objects
- 4.9.5 Exception Handlers
- 4.9.6 Exceptions and finally
4.10 Limitations of the Java Virtual Machine
- 5 Loading, Linking, and Initializing
- 5.1 The Runtime Constant Pool
- 5.2 Virtual Machine Start-up
- 5.3 Creation and Loading
- 5.3.1 Loading Using the Bootstrap Class Loader
- 5.3.2 Loading Using a User-defined Class Loader
- 5.3.3 Creating Array Classes
- 5.3.4 Loading Constraints
- 5.3.5 Deriving a Class from a
class File Representation 5.4 Linking
- 5.4.1 Verification
- 5.4.2 Preparation
- 5.4.3 Resolution
- 5.4.4 Access Control
5.5 Initialization
- 5.6 Binding Native Method Implementations
- 6 The Java Virtual Machine Instruction Set
- 6.1 Assumptions: The Meaning of "Must"
- 6.2 Reserved Opcodes
- 6.3 Virtual Machine Errors
- 6.4 Format of Instruction Descriptions
- 7 Compiling for the Java Virtual Machine
- 7.1 Format of Examples
- 7.2 Use of Constants, Local Variables, and Control Constructs
- 7.3 Arithmetic
- 7.4 Accessing the Runtime Constant Pool
- 7.5 More Control Examples
- 7.6 Receiving Arguments
- 7.7 Invoking Methods
- 7.8 Working with Class Instances
- 7.9 Arrays
- 7.10 Compiling Switches
- 7.11 Operations on the Operand Stack
- 7.12 Throwing and Handling Exceptions
- 7.13 Compiling
finally
- 7.14 Synchronization
- 7.15 Compiling Nested Classes and Interfaces
- 8 Threads and Locks
- 8.1 Terminology and Framework
- 8.2 Execution Order and Consistency
- 8.3 Rules About Variables
- 8.4 Nonatomic Treatment of
double and long Variables
- 8.5 Rules About Locks
- 8.6 Rules About the Interaction of Locks and Variables
- 8.7 Rules for
volatile Variables
- 8.8 Prescient Store Operations
- 8.9 Discussion
- 8.10 Example: Possible Swap
- 8.11 Example: Out-of-Order Writes
- 8.12 Threads
- 8.13 Locks and Synchronization
- 8.14 Wait Sets and Notification
- 9 Opcode Mnemonics by Opcode
- Appendix: Summary of Clarifications and Amendments
- Index
本文来自和你在一起的博客,原文标题为《JVM调优总结(一)-- 一些概念》。 数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。 基本类型包括:byte, short, int, long, char, float, double, Boolean, returnAddress 引用类型包括:类类型,接口类型和数组。 堆与栈 堆和栈是程序运行的关键,很有必要把他们的关系说清楚。 栈是运行时的单位,而堆是存储的单位。 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。 在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线 程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象 信息。 为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗? 第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。 第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。 第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。 第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思 考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑, 放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。 在Java中,Main函数就是栈的起始点,也是程序的起始点。 程序要运行总是有一个起点的。同C语言一样,java中的Main就是那个起点。无论什么java程序,找到main就找到了程序执行的入口:) 堆中存什么?栈中存什么? 堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处:))。 为什么不把基本类型放堆中呢?因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固 定,因此栈中存储就够了,如果把他存在堆中是没有什么意义的(还会浪费空间,后面说明)。可以这么说,基本类型和对象的引用都是存放在栈中,而且都是几个 字节的一个数,因此在程序运行时,他们的处理方式是统一的。但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常 见的一个问题就是,Java中参数传递时的问题。 Java中的参数传递时传值呢?还是传引用? 要说明这个问题,先要明确两点: 1. 不要试图与C进行类比,Java中没有指针的概念 2. 程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。 明确以上两点后。Java在方法调用传递参数时,因为没有指针,所以它都是进行传值调用(这点可以参考C的传值调用)。因此,很多书里面都说Java是进行传值调用,这点没有问题,而且也简化的C中复杂性。 但是传引用的错觉是如何造成的呢?在运行栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用 值”的传值调用,即引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到堆中的对象,这个时候才 对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是堆中的数据。所以这个修改是可以保持的了。 对象,从某种意义上说,是由基本类型组成的。可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树 的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内 容。
堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。而堆是为栈进行数据存储服务,说白了堆就是一块共享的内存。不过,正是因为堆和栈的分离的思想,才使得Java的垃圾回收成为可能。 Java中,栈的大小通过-Xss来设置,当栈中存储数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常。常见的出现这个异常的是无法返回的递归,因为此时栈中保存的信息都是方法返回的记录点。
二 JAVA垃圾收集器 2.1 垃圾收集简史 垃圾收集提供了内存管理的机制,使得应用程序不需要在关注内存如何释放,内存用完后,垃圾收集会进行收集,这样就减轻了因为人为的管理内存而造成的 错误,比如在C++语言里,出现内存泄露时很常见的。Java语言是目前使用最多的依赖于垃圾收集器的语言,但是垃圾收集器策略从20世纪60年代就已经 流行起来了,比如Smalltalk,Eiffel等编程语言也集成了垃圾收集器的机制。 2.2 常见的垃圾收集策略 所有的垃圾收集算法都面临同一个问题,那就是找出应用程序不可到达的内存块,将其释放,这里面得不可到达主要是指应用程序已经没有内存块的引用了, 而在JAVA中,某个对象对应用程序是可到达的是指:这个对象被根(根主要是指类的静态变量,或者活跃在所有线程栈的对象的引用)引用或者对象被另一个可 到达的对象引用。 2.2.1 Reference Counting(引用计数) 引用计数是最简单直接的一种方式,这种方式在每一个对象中增加一个引用的计数,这个计数代表当前程序有多少个引用引用了此对象,如果此对象的引用计数变为0,那么此对象就可以作为垃圾收集器的目标对象来收集。 优点: 简单,直接,不需要暂停整个应用 缺点: 1.需要编译器的配合,编译器要生成特殊的指令来进行引用计数的操作,比如每次将对象赋值给新的引用,或者者对象的引用超出了作用域等。 2.不能处理循环引用的问题 2.2.2 跟踪收集器 跟踪收集器首先要暂停整个应用程序,然后开始从根对象扫描整个堆,判断扫描的对象是否有对象引用,这里面有三个问题需要搞清楚: 1.如果每次扫描整个堆,那么势必让GC的时间变长,从而影响了应用本身的执行。因此在JVM里面采用了分代收集,在新生代收集的时候minor gc只需要扫描新生代,而不需要扫描老生代。 2.JVM采用了分代收集以后,minor gc只扫描新生代,但是minor gc怎么判断是否有老生代的对象引用了新生代的对象,JVM采用了卡片标记的策略,卡片标记将老生代分成了一块一块的,划分以后的每一个块就叫做一个卡 片,JVM采用卡表维护了每一个块的状态,当JAVA程序运行的时候,如果发现老生代对象引用或者释放了新生代对象的引用,那么就JVM就将卡表的状态设 置为脏状态,这样每次minor gc的时候就会只扫描被标记为脏状态的卡片,而不需要扫描整个堆。具体如下图: 3.GC在收集一个对象的时候会判断是否有引用指向对象,在JAVA中的引用主要有四种:Strong reference,Soft reference,Weak reference,Phantom reference. ◆ Strong Reference 强引用是JAVA中默认采用的一种方式,我们平时创建的引用都属于强引用。如果一个对象没有强引用,那么对象就会被回收。 - public void testStrongReference(){
- Object referent = new Object();
- Object strongReference = referent;
- referent = null;
- System.gc();
- assertNotNull(strongReference);
- }
◆ Soft Reference 软引用的对象在GC的时候不会被回收,只有当内存不够用的时候才会真正的回收,因此软引用适合缓存的场合,这样使得缓存中的对象可以尽量的再内存中待长久一点。 - Public void testSoftReference(){
- String str = "test";
- SoftReference<String> softreference = new SoftReference<String>(str);
- str=null;
- System.gc();
- assertNotNull(softreference.get());
- }
◆ Weak reference 弱引用有利于对象更快的被回收,假如一个对象没有强引用只有弱引用,那么在GC后,这个对象肯定会被回收。 - Public void testWeakReference(){
- String str = "test";
- WeakReference<String> weakReference = new WeakReference<String>(str);
- str=null;
- System.gc();
- assertNull(weakReference.get());
- }
◆ Phantom reference 2.2.2.1 Mark-Sweep Collector(标记-清除收集器) 标记清除收集器最早由Lisp的发明人于1960年提出,标记清除收集器停止所有的工作,从根扫描每个活跃的对象,然后标记扫描过的对象,标记完成以后,清除那些没有被标记的对象。 优点: 1 解决循环引用的问题 2 不需要编译器的配合,从而就不执行额外的指令 缺点: 1.每个活跃的对象都要进行扫描,收集暂停的时间比较长。 2.2.2.2 Copying Collector(复制收集器)复制收集器将内存分为两块一样大小空间,某一个时刻,只有一个空间处于活跃的状态,当活跃的空间满的时候,GC就会将活 跃的对象复制到未使用的空间中去,原来不活跃的空间就变为了活跃的空间。复制收集器具体过程可以参考下图: 优点: 1 只扫描可以到达的对象,不需要扫描所有的对象,从而减少了应用暂停的时间 缺点: 1.需要额外的空间消耗,某一个时刻,总是有一块内存处于未使用状态 2.复制对象需要一定的开销 2.2.2.3 Mark-Compact Collector(标记-整理收集器)标记整理收集器汲取了标记清除和复制收集器的优点,它分两个阶段执行,在第一个阶段,首先扫描所有活跃的对象,并 标记所有活跃的对象,第二个阶段首先清除未标记的对象,然后将活跃的的对象复制到堆得底部。标记整理收集器的过程示意图请参考下图:Mark- compact策略极大的减少了内存碎片,并且不需要像Copy Collector一样需要两倍的空间。
垃圾收集器策略从20世纪60年代就已经流行起来了,相比于其他编程语言,Java语言是目前使用最多的依赖于垃圾收集器的语言。 AD: JVM内存模型是Java的核心技术之一,之前51CTO曾为大家介绍过JVM分代垃圾回收策略的基础概念,现在很多编程语言都引入了类似Java JVM的内存模型和垃圾收集器的机制,下面我们将主要针对Java中的JVM内存模型及垃圾收集的具体策略进行综合的分析。 一 JVM内存模型 1.1 Java栈 Java栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程。它主要用来存储线程执行过程中的局部变量,方法的返回 值,以及方法调用上下文。栈空间随着线程的终止而释放。StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM就会抛 出此异常,这种情况一般是死递归造成的。 1.2 堆 Java中堆是由所有的线程共享的一块内存区域,堆用来保存各种JAVA对象,比如数组,线程对象等。 1.2.1 Generation JVM堆一般又可以分为以下三部分: ◆ Perm Perm代主要保存class,method,filed对象,这部门的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服 务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了 perm中,这种情况下,一般重新启动应用服务器可以解决问题。 ◆ Tenured Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。 ◆ Young Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中Survivor区间中,某一时刻只有其中一个是被使用的, 另外一个留做垃圾收集时复制对象用,在Young区间变满的时候,minor GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到 Tenured区间。 1.2.2 Sizing the Generations JVM提供了相应的参数来对内存大小进行配置。正如上面描述,JVM中堆被分为了3个大的区间,同时JVM也提供了一些选项对Young,Tenured的大小进行控制。 ◆ Total Heap -Xms :指定了JVM初始启动以后初始化内存 -Xmx:指定JVM堆得最大内存,在JVM启动以后,会分配-Xmx参数指定大小的内存给JVM,但是不一定全部使用,JVM会根据-Xms参数来调节真正用于JVM的内存 -Xmx -Xms之差就是三个Virtual空间的大小 ◆ Young Generation -XX:NewRatio=8意味着tenured 和 young的比值8:1,这样eden+2*survivor=1/9 堆内存 -XX:SurvivorRatio=32意味着eden和一个survivor的比值是32:1,这样一个Survivor就占Young区的1/34. -Xmn 参数设置了年轻代的大小 ◆ Perm Generation -XX:PermSize=16M -XX:MaxPermSize=64M Thread Stack -XX:Xss=128K 1.3 堆栈分离的好处 呵呵,其它的先不说了,就来说说面向对象的设计吧,当然除了面向对象的设计带来的维护性,复用性和扩展性方面的好处外,我们看看面向对象如何巧妙的 利用了堆栈分离。如果从JAVA内存模型的角度去理解面向对象的设计,我们就会发现对象它完美的表示了堆和栈,对象的数据放在堆中,而我们编写的那些方法 一般都是运行在栈中,因此面向对象的设计是一种非常完美的设计方式,它完美的统一了数据存储和运行。
转自:http://hi.baidu.com/fecasmoy123/blog/item/7f91fd8da34ac918b31bbabf.html
servlet 2.3 新增功能:
2000年10月份出来
Servlet API 2.3中最重大的改变是增加了filters
servlet 2.4 新增功能:
2003年11月份出来
1、web.xml DTD改用了XML Schema;
Servlet 2.3之前的版本使用DTD作为部署描述文件的定义,其web.xml的格式为如下所示:
<?xml version="1.0" encoding="IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//sunMicrosystems,Inc.//DTD WebApplication 2.3f//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.3.dtd">
<web-app>
.......
</web-app>
Servlet 2.4版首次使用XML Schema定义作为部署描述文件,这样Web容器更容易校验web.xml语法。同时XML Schema提供了更好的扩充性,其web.xml中的格式如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:workflow="http://www.workflow.com"
xmins:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
.........
</web-app>
注意: 改为Schema后主要加强了两项功能:
(1) 元素不依照顺序设定
(2) 更强大的验证机制
主要体现在:
a.检查元素的值是否为合法的值
b.检查元素的值是否为合法的文字字符或者数字字符
c.检查Servlet,Filter,EJB-ref等等元素的名称是否唯一
2.新增Filter四种设定:REQUEST、FORWARD、INCLUDE和ERROR。
3.新增Request Listener、Event和Request Attribute Listener、Enent。
4.取消SingleThreadModel接口。当Servlet实现SingleThreadModel接口时,它能确保同时间内,只能有一个thread执行此Servlet。
5.<welcome-file-list>可以为Servlet。
6.ServletRequest接口新增一些方法。
public String getLocalName()
public String getLocalAddr()
public int getLocalPort()
public int getRemotePort()
Servlet 2.5的新特征
2005年9月发布Servlet 2.5
Servlet2.5一些变化的介绍:
1) 基于最新的J2SE 5.0开发的。
2) 支持annotations 。
3) web.xml中的几处配置更加方便。
4) 去除了少数的限制。
5) 优化了一些实例
servlet的各个版本对监听器的变化有:
(1)servlet2.2和jsp1.1
新增Listener:HttpSessionBindingListener
新增Event: HttpSessionBindingEvent
(2)servlet2.3和jsp1.2
新增Listener:ServletContextListener,ServletContextAttributeListener
,HttpSessionListener,HttpSessionActivationListener,HttpSessionAttributeListener
新增Event: ServletContextEvent,ServletContextAttributeEvent,HttpSessionEvent
(3)servlet2.4和jsp2.0
新增Listener:ServletRequestListener,ServletRequestAttribureListener
新增Event: ServletRequestEvent,ServletRequestAttributeEvent
摘要: PostgreSQL与MySQL数据库的比较 阅读全文
Two Simple Rules for HTTP Caching
In practice, you only need two settings to optimize caching:
- Don’t cache HTML
- Cache everything else forever
“Wooah…hang on!”, we hear you say. “Cache all my scripts and images forever?“
Yes, that’s right. You don’t need anything else in between. Caching
indefinitely is fine as long as you don’t allow your HTML to be cached.
“But what about if I need to issue code patches to my JavaScript? I can’t allowbrowsers to hold on to all my images either. I often need to update those as well.”
Simple – just change the URL of the item in your HTML and it will bypass the existing entry in the cache.
In practice, caching ‘forever’ typically means setting an Expires header value of Sun, 17-Jan-2038 19:14:07 GMT
since that’s the maximum value supported by the 32 bit Unix time/date
format. If you’re using IIS6 you’ll find that the UI won’t allow
anything beyond 31-Dec-2035. The advantage of setting long expiry dates
is that the content can be read from the local browser cache whenever
the user revisits the web page or goes to another page that uses the
same images, script or CSS files.
You’ll see long expiry dates like this if you look at a Google web
page with HttpWatch. For example, here are the response headers used for
the main Google logo on the home page:
If Google needs to change the logo for a special occasion like
Halloween they just change the name of the file in the page’s HTML to
something like halloween2007.gif.
The diagram below shows how a JavaScript file is loaded into the browser cache on the first visit to a web page:
On any subsequent visits the browser only has to fetch the page’s HTML:
The JavaScript file can be read directly from the browser cache on
the user’s hard disk. This avoids a network round trip and is typically
100 to 1000 times faster than downloading the file over a broadband
connection.
The key to this caching scheme is to keep tight control over your
HTML as it holds the references to everything else on your web site. One
way to do this is to ensure that your pages have a Cache-Control: no-cache header. This will prevent any caching of the HTML and will ensure the browser requests the page’s HTML every time.
If you do this, you can update any content on the page just by
changing the URL that refers to it in the HTML. The old version will
still be in the browser’s cache, but the updated version will be
downloaded because of the modified URL.
For instance, if you had a file called topMenu.js and you fixed some
bugs in it, you might rename the file topMenu-v2.js to force it to be
downloaded:
Now this is all very well, but whenever there’s a discussion of
longer expiration times, the marketing people get very twitchy and
concerned that they won’t be able to re-brand a site if stylesheets and
images are cached for long periods of time.
In fact, choosing an expiration time of anything other than zero or
infinite is inherently uncertain. The only way to know exactly when you
can release a new version to all users simultaneously is to choose a
specific time of day for your cache expiry; say midnight. It’s better to
set indefinite caching on all your page-linked items so that you get
the maximum amount of caching, and then force updates as required.
Now, by this point, you might have the marketing types on board but
you’ll be losing the developers. The developers by now are seeing all
the extra work involved in changing the filenames of all their CSS,
javascript and images both in their source controlled projects and in
their deployment scripts.
So here’s the icing on the cake; you don’t actually need to
change the filename, just the URL. A simple way to do this is to append a
query string parameter onto the end of the existing URL when the
resource has changed.
Here’s the previous example that updated a JavaScript file. The
difference this time is that it uses a query string parameter ‘v2′ to
bypass the existing cache entry:
The web server will simply ignore the query string parameter unless you have chosen to do anything with it programmatically.
There’s one final optimization you can make. The Cache-Control: no-cache
response header works well for dynamic pages as it ensures that pages
will always be refreshed from the server; even when pressing the Back
button. However, for HTML that changes less frequently it is better to
use the Last-Modified header instead. This will avoid a
complete download of the page’s HTML, if it has not changed since it was
last cached by the browser.
The Last-Modified header is added automatically by IIS
for static HTML files and can be added programmatically in dynamic pages
(e.g. ASPX and PHP). When this header is present, the browser will
revalidate the local, cached copy of an HTML page in each new browser
session. If the page is unchanged the web server returns a 304 Not Modified response indicating the browser can use the cached version of the page.
So to summarize:
- Don’t cache HTML
- Use
Cache-Control: no-cache for dynamic HTML pages
- Use the
Last-Modified header with the current file time for static HTML
- Cache everything else forever
- For all other file types set an
Expires header to the maximum future date your web server will allow
- Modify URLs by appending a query string in your HTML to any page element you wish to ‘expire’ immediately.
当你建立好一个WEB服务后,通常有两个类型的缓存需要配置:
- 设置网站有更新的时候html资源马上过期,以便正在浏览的用户可以很快地得到更新.
- 设置所有其它资源(例如图片,CSS,javascript脚本)在一定时间后过期.
这个缓存方案涵盖Two Simple Rules for HTTP Caching文章中提到关于如何处理更新的一些思想.
现在HttpWatch 6.0支持Firefox了,我们想探讨一下Firefox在处理缓存上与IE有些什么不同.设置较长过期时间的使用方式(上面第二条)仍可以直接用于Firefox,但配置1在两者之间还是存在细微差别的.
在之前的文章 中,我们把第一条划分为:
- 某些时候动态HTML页面需要即时从服务器更新以备随时显示-甚至是使用后退按钮的时候.例如,显示银行帐号的状态或在线订单.
- 静态HTML页面,比如联系,FAQs或者站点地图等页面,如果它们设置了
Last-Modified响应头 ,允许浏览器在需要的时候重新校验,就可以利用到缓存.
一:火狐机制
本文剩下部分探讨了Firefox中影响HTML页面缓存的两个重要不同点.
1. 使用no-cache防止Firefox缓存无效
你可以简单地设置如下的响应头预防IE缓存任何东西:
Cache-Control: no-cache
使用了这个响应头的页面不会保存在缓存里,IE总会重新从服务器加载;即使你使用后退按钮.下面这个例子使用HttpWatch监听一个网上商店,当我们在提交订单表单后点击后退按钮,结果如下图:
然而,这个响应头却不能防止Firefox的缓存.这意味着,Firefox在正常访问的情况下,将一直使用缓存的页面,直到它发送GET请求重新检验.并且,如果是通过后退按钮访问页面,Firefox不会再次访问服务器,而是简单直接地从缓存加载.
那怎样才能关掉Firefox中的缓存呢? 答案很简单,关不了. 因为Firefox依靠缓存中的副本为"文件->另存为","查看源代码"这样的操作服务.但是,你可以控制页面缓存到哪里及那些缓存条目可以用于显示.
下面响应头在Firefox中可以防止持久化的缓存,强制页面被缓存到内存中:
Cache-Control:no-store
这个头也可以防止使用后退按钮时访问了缓存页面,它将触发一个HTTP GET请求.
这两个响应头的值组合使用可以在IE与Firefox得到期待的结果:
Cache-Control: no-cache, no-store
如下HttpWatch响应头标签所示:
2. 如果没有设置过期时间Firefox会为你设置一个
当IE遇到没有Expires头的http响应时,它就认为永远不能自动使用缓存条目,直到它重新从服务校验.由于IE的临时文件的一个设置项"检查所在网页的较新版本"默认为"自动",所以通常都是一个会话做一次.
这就为控制静态的html内容的缓存提供了一个合理的方式.用户新打开的IE会得到html的最新版本,而缓存的版本就在关闭IE前会一直被使用.
Firefox处理缺失Expires头的方式不同.如果影响中有Last-Modified头它就会使用HTTP 1.1规范RFC2616中指定的一个尝试性的过期值:
(引用规范:)
并且,如果响应中有Last-Modified时间值,尝试性的过期值不能超过这个值到现在时间间隔的一个比率,一般设置这个比率为10%.
计算方式如下:(默认过期时间)
过期时间 = 现在时间 + 0.1 * (Last-Modified到现在的时间差)
例如,如果你的静态HTML文件上次修改时间是100天前,那过期时间就是10天之后.下面的示例是一个没有Expires头页面的HttpWatch缓存标签:
Firefox自动设置了过期时间为8天后,因为这个页面大概80天没有被修改过了.
这意味着,为了保持控制好你的HTML页面,正如我们在 Two Simple Rules for HTTP Caching 文章中讨论过的,你最好为你的静态资源如HTML,图片,CSS文件等,在你的WEB服务器设置一个合适的Expires值.
结论
为了确保IE与Firefox的缓存行为一致,你应该:
- 总是指定一个Expires头. 一般设置-1使用html页面能即时刷新或者对其它如图片,CSS,javascript脚本资源设置一个特定的过期时间
- 如果你要强制页面刷新,甚至是点击后台按钮的时候,那就设置 Cache-Control: no-cache, no-store
----------------------------------
二:IE机制
1:每次都会到服务器检查最后一次修改时间是否变化 无变化返回304
2:IE启动时检查
3:自动检查
3.1:根据IE的默认在浏览器不关闭时,再次访问同一链接都读本地cache
3.2:IE重启时候会根据服务器设置的expire值去判断是否去服务器请求判断,无变化返回304
4:第一次下载后从不检查
最好的机制是 3 。根据服务器的设定机制走,会提高访问速度。
提高访问速度,最好的办法就是减少TCP通信。
-------------------------
三:服务器的设置
参考: http://hi.baidu.com/%C8%FD%BE%D6%CE%AA%B6%FE/blog/item/30dae1325363ed92a8018e5c.html
-------------------------
四:服务端设置和客户端的关联
服务器的设置告知浏览器怎么做缓存,缓存时间是多长。客户端同时也可以是CDN。
-------------------------
五:工具类分析
IE httpwatch ,FF httpfox YSlow firebug
|
|
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
常用链接
留言簿(12)
随笔分类
随笔档案
文章分类
文章档案
相册
收藏夹
好友的blog
技术网站
搜索
积分与排名
最新评论
阅读排行榜
评论排行榜
|
|