Let's Go!

发布新日志

  • java取版本号

    2015-07-14 14:39:55


    java取版本号

    需要注意的是
    java -version的输出结果不在标准输出,在标准错误输出,
    所以需要将标准错误输出重定向到标准输出,然后进行处理


    $ java -version 2>&1 | awk '/version/{print $3}'
    这样更简单



    ------------------------------------------------------
    java -version 2>&1 | grep version | awk '{print $3}'
    "1.7.0_55"


    java -version 2>&1 | awk -F\" '/version/{print $2}'
    1.7.0_67
  • java jvm内存管理/gc策略/参数设置(二)

    2011-07-29 13:02:34

     

    1.GC的类型以及对应日志的解释

    http://www.iteye.com/topic/603347

    在大型的java运用中,要进行程序调优,指定一个合适的垃圾回收机制是必不可少的,那如何可确定某gc是否使得程序最优化呢?我们可以查看jvm打印出的gc日志来分析,并做出进一步优化,而目前并没有一篇文章明确的指明java中各种gc算法打印出日志的格式,以及如何阅读。所以接下来本文将试着边介绍各种垃圾回收机制边解释该回收机制下log的格式。
    1,垃圾收集算法
     1.1 引用计数法(Reference Counting Collector)
       
    系统记录对象被应用的次数,当应用次数为0时,就可以将该对象所占内存回收。该算法可以不用暂停运用,但缺点是无法解决重复运用的问题。所以java并没有提供此类垃圾回收器。
      1.2  tracing算法(Tracing Collector)
           tracing算法的垃圾收集器从根集开始扫描,识别出哪些对象可达,哪 些对象不可达,并用某种方式标记可达对象。
      1.2.1 
    复制( Copying )算法
        
    复制算法将堆等分成2个区域,一个区域含有现在的数据对象(ToSpace),而另一个区域包含废弃的数据(FromSpace)。复制算法将存活的对象从FromSpace复制到ToSpace,然后切换Fromspace和ToSpace的指针,以前的FromSpace变为现在的ToSpace区域。

      1.2.2 标记-整理( Mark-Compact )算法
           
      1.2.3 标记-清除(Mark-Sweep)

    Using the -XX flags for our collectors for jdk6,

    • UseSerialGC is "Serial" + "Serial Old"
    • UseParNewGC is "ParNew" + "Serial Old"
    • UseConcMarkSweepGC is "ParNew" + "CMS" + "Serial Old". "CMS" is used most of the time to collect the tenured generation. "Serial Old" is used when a concurrent mode failure occurs.
    • UseParallelGC is "Parallel Scavenge" + "Serial Old"
    • UseParallelOldGC is "Parallel Scavenge" + "Parallel Old" 

     

    SerailGC

    1,Serial Young GC 
    0.246: [GC 0.246: [DefNew: 1403K->105K(1984K), 0.0109275 secs] 1403K->1277K(6080K), 0.0110143 secs] 
    2, Serial Olg Gc

    1.133: [GC 1.133: [DefNew: 960K->64K(960K), 0.0012208 secs]1.135: [Tenured: 7334K->7142K(7424K), 0.0213756 secs] 7884K->7142K(8384K), [Perm : 364K->364K(12288K)], 0.0226997 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

     

    Parrallel GC serailGC的适应muti Core的加强版,就是在minorGC时候采用并行的进行收集,而fullGC并没有改变

    Parralllel Compacting GC 在parrallelGC的基础上将fullgc也变为并发的了
    With the parallel compacting collector, the old and permanent generations are collected in a stop-theworld,mostly parallel fashion with sliding compaction. The collector utilizes three phases. First, each generation is logically divided into fixed-sized regions. In the marking phase, the initial set of live objects directly reachable from the application code is divided among garbage collection threads, and then all live objects are marked in parallel. As an object is identified as live, the data for the region it is in is updated with information about the size and location of the object.备注 待翻译



     

    Concurrent Mark-Sweep (CMS) Collector   有一种需求是应用的相应时间比应用的吞吐重要,为了满足这种需求,jvm提供了该场景下的垃圾收集器CMS,使用该垃圾收集器的时候minorGC和 ParralelGC当中采用的一样,只是在老生代更换了不同的算法。

    CMS将老生代的回收分为4个阶段 其中只有2个阶段是要stop-the-world的,而其余阶段是不需要的,因此降低了系统暂停时间,缺点是在其余的2个阶段会更应用抢jvm资源。

     
      

    从上图可以看出,CMS的运行过程。

    A collection cycle for the CMS collector starts with a short pause, called the initial mark, that
    identifies the initial set of live objects directly reachable from the application code. Then,
    during the concurrent marking phase, the collector marks all live objects that are transitively
    reachable from this set. Because the application is running and updating reference fields while the marking phase is taking place, not all live objects are guaranteed to be marked at the end of the concurrent marking phase. To handle this, the application stops again for a second pause, called remark, which finalizes marking by revisiting any objects that were modified during the concurrent marking phase. Because the remark pause is more substantial than the initial mark, multiple threads are run in parallel to increase its efficiency. 备注 待翻译


    Concurrent Mark-Sweep GC log format:



    Full GC 被调用的出现情况
     

    promotion failed( mark-sweep-compact stop-the-world ) ParNew (promotion failed):  当老生代空闲空间存在碎片,导致没有足够大的连续空间开存放新生代对象的升级时,机会触发promotion failed。 此时调用一个Mark-Compact 垃圾收集器是很有必要的。(默认采用 Serial Old GC)

    106.641: [GC 106.641: [ParNew (promotion failed): 14784K->14784K(14784K), 0.0370328 secs]106.678: [CMS106.715: [CMS-concurrent-mark: 0.065/0.103 secs] [Times: user=0.17 sys=0.00, real=0.11 secs]
     (concurrent mode failure): 41568K->27787K(49152K), 0.2128504 secs] 52402K->27787K(63936K), [CMS Perm : 2086K->2086K(12288K)], 0.2499776 secs] [Times: user=0.28 sys=0.00, real=0.25 secs]

    full promotion guarantee failure ( concurrent mode failure ): 当垃圾回收算法预测在下一次Conc-Mark-Sweep算法调用之前,老生代的空余空间将会被系统占用光。为了解决这一问题,垃圾回收算法进入conccurent mode failure状态,调用一个 stop-the-world(serail Old GC)来清理系统Heap。


    eg:为了触发这种情况 我们先分配64m内存给jvm,然后新生代和老年代的占用比例为7,即老年代用了7*8=58 触发concurrent mode failure的情况:

     

     

    public class FullPromotionGuaranteeFailure
    {
        public static void main(String[] args)
        {
        List<byte[]> bytesList = new ArrayList<byte[]>();
        for (int i = 0; i < 7 * 8 * 1024; i++)
        {
            bytesList.add(new byte[1024]);
        }
        
        //bytesList = null; 没有必要的 gc会知道函数里面变量是否还会被引用
        byte[] bytes = new byte[16 * 1024 * 1024];
        String.valueOf(bytes[0]);
        }
    }


    运行时JVM参数:
    -Xmx64m -Xms64m -XX:NewRatio=7 -XX:MaxTenuringThreshold=0 -verbose:gc  -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+UseConcMarkSweepGC
    -Xloggc:full-promotion-guarantee-failure.log
    full-promotion-guarantee-failure.log 内容


    0.195: [GC 0.195: [ParNew: 2986K->2986K(8128K), 0.0000083 secs]0.195: [CMS0.212: [CMS-concurrent-preclean: 0.011/0.031 secs] [Times: user=0.03 sys=0.02, real=0.03 secs]
     (concurrent mode failure): 56046K->138K(57344K), 0.0271519 secs] 59032K->138K(65472K), [CMS Perm : 2079K->2078K(12288K)], 0.0273119 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 

     

     

    调用Serial Old GC 是费时并且暂停了整个应用,这显然不是我们想看到的。为了 避免办法出现( concurrent mode failure )这种情况,可以参考bluedavy的该篇blog GC 策略的调优


    Stop-the-world GC happens when a JNI critical section is released . Here again the young generation collection failed due to "full promotion guarantee failure" and then the Full GC was invoked.

    283.736: [Full GC 283.736: [ParNew: 261599K->261599K(261952K), 0.0000615 secs]
    826554K->826554K(1048384K), 0.0003259 secs]
    GC locker: Trying a full collection because scavenge failed





    参考链接:
    1. Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning
    2. Turbo-charging Java HotSpot Virtual Machine, v1.4.x to Improve the Performance and Scalability of Application Servers
    3. Understanding Concurrent Mark Sweep Garbage Collector Logs
    4. What the Heck's a Concurrent Mode?
    5. https://java.sun.com/j2se/reference/...whitepaper.pdf  
    6. http://blogs.sun.com/jonthecollector...the_sum_of_the  
    7. http://blogs.sun.com/jonthecollector...t_the_heck_s_a

     

     

    2. CMS gc实践总结

    http://www.iteye.com/topic/473874

    首先感谢阿宝同学的帮助,我才对这个gc算法的调整有了一定的认识,而不是停留在过去仅仅了解的阶段。在读过sun的文档和跟阿宝讨论之后,做个小小的总结,如果有谬误,敬请指正。
        CMS,全称Concurrent Low Pause Collector,是jdk1.4后期版本开始引入的新gc算法,在jdk5和jdk6中得到了进一步改进,它的主要适合场景是对响应时间的重要性需求大于对吞吐量的要求,能够承受垃圾回收线程和应用线程共享处理器资源,并且应用中存在比较多的长生命周期的对象的应用。CMS是用于对tenured generation的回收,也就是年老代的回收,目标是尽量减少应用的暂停时间,减少full gc发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代。在我们的应用中,因为有缓存的存在,并且对于响应时间也有比较高的要求,因此希望能尝试使用CMS来替代默认的server型JVM使用的并行收集器,以便获得更短的垃圾回收的暂停时间,提高程序的响应性。
        CMS并非没有暂停,而是用两次短暂停来替代串行标记整理算法的长暂停,它的收集周期是这样:
        初始标记(CMS-initial-mark) -> 并发标记(CMS-concurrent-mark) -> 重新标记(CMS-remark) -> 并发清除(CMS-concurrent-sweep) ->并发重设状态等待下次CMS的触发(CMS-concurrent-reset)
        其中的1,3两个步骤需要暂停所有的应用程序线程的。第一次暂停从root对象开始标记存活的对象,这个阶段称为初始标记;第二次暂停是在并发标记之后,暂停所有应用程序线程,重新标记并发标记阶段遗漏的对象(在并发标记阶段结束后对象状态的更新导致)。第一次暂停会比较短,第二次暂停通常会比较长,并且 remark这个阶段可以并行标记。

        而并发标记、并发清除、并发重设阶段的所谓并发,是指一个或者多个垃圾回收线程和应用程序线程并发地运行,垃圾回收线程不会暂停应用程序的执行,如果你有多于一个处理器,那么并发收集线程将与应用线程在不同的处理器上运行,显然,这样的开销就是会降低应用的吞吐量。Remark阶段的并行,是指暂停了所有应用程序后,启动一定数目的垃圾回收进程进行并行标记,此时的应用线程是暂停的。

        CMS的young generation的回收采用的仍然是并行复制收集器,这个跟Paralle gc算法是一致的。

        下面是参数介绍和遇到的问题总结,

    1、启用CMS:-XX:+UseConcMarkSweepGC。 咳咳,这里犯过一个低级错误,竟然将+号写成了-号

     

    2。CMS默认启动的回收线程数目是  (ParallelGCThreads + 3)/4) ,如果你需要明确设定,可以通过-XX:ParallelCMSThreads=20来设定,其中ParallelGCThreads是年轻代的并行收集线程数


    3、CMS是不会整理堆碎片的,因此为了防止堆碎片引起full gc,通过会开启CMS阶段进行合并碎片选项:-XX:+UseCMSCompactAtFullCollection,开启这个选项一定程度上会影响性能,阿宝的blog里说也许可以通过配置适当的CMSFullGCsBeforeCompaction来调整性能,未实践。

    4.为了减少第二次暂停的时间,开启并行remark: -XX:+CMSParallelRemarkEnabled。如果remark还是过长的话,可以开启-XX:+CMSScavengeBeforeRemark选项,强制remark之前开始一次minor gc,减少remark的暂停时间,但是在remark之后也将立即开始又一次minor gc。

    5.为了避免Perm区满引起的full gc,建议开启CMS回收Perm区选项:

    +CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled


    6.默认CMS是在tenured generation沾满68%的时候开始进行CMS收集,如果你的年老代增长不是那么快,并且希望降低CMS次数的话,可以适当调高此值:
    -XX:CMSInitiatingOccupancyFraction=80

    这里修改成80%沾满的时候才开始CMS回收。

    7.年轻代的并行收集线程数默认是(cpu <= 8) ? cpu : 3 + ((cpu * 5) / 8),如果你希望降低这个线程数,可以通过-XX:ParallelGCThreads= N 来调整。

    8.进入重点,在初步设置了一些参数后,例如:

     

    Java代码 复制代码 收藏代码
    1. -server -Xms1536m -Xmx1536m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=64m   
    2. -XX:MaxPermSize=64m -XX:-UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection   
    3. -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSParallelRemarkEnabled   
    4. -XX:SoftRefLRUPolicyMSPerMB=0  

     

    需要在生产环境或者压测环境中测量这些参数下系统的表现,这时候需要打开GC日志查看具体的信息,因此加上参数:

    -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/home/test/logs/gc.log

    在运行相当长一段时间内查看CMS的表现情况,CMS的日志输出类似这样:

     

    Java代码 复制代码 收藏代码
    1. 4391.322: [GC [1 CMS-initial-mark: 655374K(1310720K)] 662197K(1546688K), 0.0303050 secs] [Times: user=0.02 sys=0.02, real=0.03 secs]   
    2. 4391.352: [CMS-concurrent-mark-start]   
    3. 4391.779: [CMS-concurrent-mark: 0.427/0.427 secs] [Times: user=1.24 sys=0.31, real=0.42 secs]   
    4. 4391.779: [CMS-concurrent-preclean-start]   
    5. 4391.821: [CMS-concurrent-preclean: 0.040/0.042 secs] [Times: user=0.13 sys=0.03, real=0.05 secs]   
    6. 4391.821: [CMS-concurrent-abortable-preclean-start]   
    7. 4392.511: [CMS-concurrent-abortable-preclean: 0.349/0.690 secs] [Times: user=2.02 sys=0.51, real=0.69 secs]   
    8. 4392.516: [GC[YG occupancy: 111001 K (235968 K)]4392.516: [Rescan (parallel) , 0.0309960 secs]4392.547: [weak refs processing, 0.0417710 secs] [1 CMS-remark: 655734K(1310720K)] 766736K(1546688K), 0.0932010 secs] [Times: user=0.17 sys=0.00, real=0.09 secs]   
    9. 4392.609: [CMS-concurrent-sweep-start]   
    10. 4394.310: [CMS-concurrent-sweep: 1.595/1.701 secs] [Times: user=4.78 sys=1.05, real=1.70 secs]   
    11. 4394.310: [CMS-concurrent-reset-start]   
    12. 4394.364: [CMS-concurrent-reset: 0.054/0.054 secs] [Times: user=0.14 sys=0.06, real=0.06 secs]  


    其中可以看到CMS-initial-mark阶段暂停了0.0303050秒,而CMS-remark阶段暂停了0.0932010秒,因此两次暂停的总共时间是0.123506秒,也就是123毫秒左右。两次短暂停的时间之和在200以下可以称为正常现象。

    但是你很可能遇到两种fail引起full gc:Prommotion failed和Concurrent mode failed。

    Prommotion failed的日志输出大概是这样:

     

    Java代码 复制代码 收藏代码
    1. [ParNew (promotion failed): 320138K->320138K(353920K), 0.2365970 secs]42576.951: [CMS: 1139969K->1120688K(   
    2. 166784K), 9.2214860 secs] 1458785K->1120688K(2520704K), 9.4584090 secs]  


    这个问题的产生是由于救助空间不够,从而向年老代转移对象,年老代没有足够的空间来容纳这些对象,导致一次full gc的产生。解决这个问题的办法有两种完全相反的倾向:增大救助空间、增大年老代或者去掉救助空间。增大救助空间就是调整-XX:SurvivorRatio参数,这个参数是Eden区和Survivor区的大小比值,默认是32,也就是说Eden区是 Survivor区的32倍大小,要注意Survivo是有两个区的,因此Surivivor其实占整个young genertation的1/34。调小这个参数将增大survivor区,让对象尽量在survitor区呆长一点,减少进入年老代的对象。去掉救助空间的想法是让大部分不能马上回收的数据尽快进入年老代,加快年老代的回收频率,减少年老代暴涨的可能性,这个是通过将-XX:SurvivorRatio 设置成比较大的值(比如65536)来做到。在我们的应用中,将young generation设置成256M,这个值相对来说比较大了,而救助空间设置成默认大小(1/34),从压测情况来看,没有出现prommotion failed的现象,年轻代比较大,从GC日志来看,minor gc的时间也在5-20毫秒内,还可以接受,因此暂不调整。

    Concurrent mode failed的产生是由于CMS回收年老代的速度太慢,导致年老代在CMS完成前就被沾满,引起full gc,避免这个现象的产生就是调小-XX:CMSInitiatingOccupancyFraction参数的值,让CMS更早更频繁的触发,降低年老代被沾满的可能。我们的应用暂时负载比较低,在生产环境上年老代的增长非常缓慢,因此暂时设置此参数为80。在压测环境下,这个参数的表现还可以,没有出现过Concurrent mode failed。


    参考资料:
    JDK5.0垃圾收集优化之--Don't Pause》 by 江南白衣
    《记一次Java GC调整经历》1,2 by Arbow
    Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning
    Tuning Garbage Collection with the 5.0 JavaTM Virtual Machine

     

     

     

    3.  通过GC输出分析内存泄露问题
    http://www.iteye.com/topic/256701

    SIP5.0以后服务的请求量爆发性增长,因此也暴露了原来没有暴露出来的问题。由于过去一般一个新版本发布周期在一个月左右,因此如果是小的内存泄露,在一个月之内重新发布以后也就看不出任何问题。

    因此这阵子除了优化Memcache客户端和SIP框架逻辑以外其他依赖部分以外,对于内存泄露的压力测试也开始实实在在的做起来。经过这次问题的定位和解决以后,大致觉得对于一个大用户量应用要放心的话,那么需要做这么几步。

    1.       在GC输出的环境下,大压力下做多天的测试。(可以在 JAVA_OPTS增加-verbose:gc -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError)

    2.       检查GC输出日志来判断是否有内存泄露。(这部分后面有详细的实例说明)

    3.       如果出现内存泄露问题,则使用jprofiler等工具来排查内存泄露点(之所以不一开始使用,因为jprofiler等工具对于压力测试有影响,使得大压力无法上去,也使问题不那么容易暴露)

    4.       解决问题,并在重复2步骤。

    这里对SIP在jdk1.5和jdk1.6下做压力测试的GC 日志来做一个实际的分析对比,通过对比来大致描述一下如何根据输出情况能够了解应用是否存在内存泄露问题。(这里的内存泄露问题就是在以前blog写过的jdk的concurrent包内LinkedBlockingQueue的poll方法存在比较严重的内存泄露,调用频率越高,内存泄露的越厉害)

    两次压力测试都差不多都是两天,测试方案如下:

    开始50个并发,每个并发每次请求完毕后休息0.1秒,10分钟后增长50个并发,按此规律增长到500并发。

    旧版本SIP是在JDK1.5环境下完成的压力测试,

    新版本SIP的JDK版本是1.6,

    压力机和以前一样,是10.2.226.40,DELL1950,8CPU,8G内存。

    压力机模拟发出对一个需要签名的API不断的调用请求。

    看看两个Log的具体内容(内容很多截取部分做分析)

    先说一下日志输出的结构:(1.6和1.5略微有一些不同,只是1.6对于时间统计更加细致)

    [GC [<collector>: <starting occupancy1> -> <ending occupancy1>, <pause time1> secs] <starting occupancy3> -> <ending occupancy3>, <pause time3> secs]

    <collector>GC收集器的名称

    <starting occupancy1> 新生代在GC前占用的内存

    <ending occupancy1> 新生代在GC后占用的内存

    <pause time1> 新生代局部收集时jvm暂停处理的时间

    <starting occupancy3> JVM Heap 在GC前占用的内存

    <ending occupancy3> JVM Heap 在GC后占用的内存

    <pause time3> GC过程中jvm暂停处理的总时间

    Jdk1.5 log:

    启动时GC输出:

    [GC [DefNew: 209792K->4417K(235968K), 0.0201630 secs] 246722K->41347K(498112K), 0.0204050 secs]

    [GC [DefNew: 214209K->4381K(235968K), 0.0139200 secs] 251139K->41312K(498112K), 0.0141190 secs]

    一句输出:

    新生代回收前209792K,回收后4417K,回收数量205375K,Heap总量回收前246722K回收后41347K,回收总量205375K。这就表示100%的收回,没有任何新生代的对象被提升到中生代或者永久区(名字说的不一定准确,只是表达意思)。

    第二句输出:

    按照分析也就只是有1K内容被提升到中生代。

    运行一段时间后:

    [GC [DefNew: 210686K->979K(235968K), 0.0257140 secs] 278070K->68379K(498244K), 0.0261820 secs]

    [GC [DefNew: 210771K->1129K(235968K), 0.0275160 secs] 278171K->68544K(498244K), 0.0280050 secs]

    第一句输出:

             新生代回收前210686K,回收后979K,回收数量209707K,Heap总量回收前278070K回收后68379K,回收总量209691K。这就表示有16k没有被回收。

    第二句输出:

             新生代回收前210771K,回收后1129K,回收数量209642K,Heap总量回收前278171K回收后68544K,回收总量209627K。这就表示有15k没有被回收。

    比较一下启动时与现在的新生代占用内存情况和Heap使用情况发现Heap的使用增长很明显,新生代没有增长,而Heap使用总量增长了27M,这就表明可能存在内存泄露,虽然每一次泄露的字节数很少,但是频率很高,大部分泄露的对象都被升级到了中生代或者持久代。

    又一段时间后:

    [GC [DefNew: 211554K->1913K(235968K), 0.0461130 secs] 350102K->140481K(648160K), 0.0469790 secs]

    [GC [DefNew: 211707K->2327K(235968K), 0.0546170 secs] 350275K->140921K(648160K), 0.0555070 secs]

    第一句输出:

             新生代回收前211554K,回收后1913K,回收数量209641K,Heap总量回收前350102K回收后140481K,回收总量209621K。这就表示有20k没有被回收。



             分析到这里就可以看出每一次泄露的内存只有10几K,但是在大压力长时间的测试下,内存泄露还是很明显的,此时Heap已经增长到了140M,较启动时已经增长了100M。同时GC占用的时间越来越长。

    后续的现象:

             后续观察日志会发现,Full GC的频率越来越高,收集所花费时间也是越来越长。(Full GC定期会执行,同时局部回收不能满足分配需求的情况下也会执行)。



    [Full GC [Tenured: 786431K->786431K(786432K), 3.4882390 secs] 1022399K->1022399K(1022400K), [Perm : 36711K->36711K(98304K)], 3.4887920 secs]

    java.lang.OutOfMemoryError: Java heap space

    Dumping heap to java_pid7720.hprof ...



             出现这个语句表示内存真的被消耗完了。

    Jdk1.6 log:



    启动时GC的输出:

    [GC [PSYoungGen: 221697K->31960K(229376K)] 225788K->36051K(491520K), 0.0521830 secs] [Times: user=0.26 sys=0.05, real=0.05 secs]

    [GC [PSYoungGen: 228568K->32752K(229376K)] 232659K->37036K(491520K), 0.0408620 secs] [Times: user=0.21 sys=0.02, real=0.04 secs]



    第一句输出:

             新生代回收前221697K,回收后31960K,回收数量189737K,Heap总量回收前225788K回收后36051K,回收总量189737K。100%被回收。



    运行一段时间后输出:

    [GC [PSYoungGen: 258944K->2536K(259328K)] 853863K->598135K(997888K), 0.0471620 secs] [Times: user=0.15 sys=0.00, real=0.05 secs]

    [GC [PSYoungGen: 259048K->2624K(259328K)] 854647K->598907K(997888K), 0.0462980 secs] [Times: user=0.16 sys=0.02, real=0.04 secs]



    第一句输出:

             新生代回收前258944K,回收后2536K,回收数量256408K,Heap总量回收前853863K回收后598135K,回收总量255728K。680K没有被回收,但这并不意味着就会产生内存泄露。同时可以看出GC回收时间并没有增加。



    在运行一段时间后输出:

    [GC [PSYoungGen: 258904K->2488K(259264K)] 969663K->713923K(1045696K), 0.0485140 secs] [Times: user=0.16 sys=0.01, real=0.04 secs]

    [GC [PSYoungGen: 258872K->2448K(259328K)] 970307K->714563K(1045760K), 0.0473770 secs] [Times: user=0.16 sys=0.01, real=0.05 secs]



    第一句输出:

             新生代回收前258904K,回收后2488K,回收数量256416K,Heap总量回收前969663K回收后713923K,回收总量255740K。676K没有被回收,同时总的Heap也有所增加。

             此时看起来好像和1.5的状况一样。但是查看了一下Full GC的执行还是400-500次GC执行一次,因此继续观察。



    运行一天多以后输出:

    [GC [PSYoungGen: 257016K->3304K(257984K)] 1019358K->766310K(1044416K), 0.0567120 secs] [Times: user=0.18 sys=0.01, real=0.06 secs]

    [GC [PSYoungGen: 257128K->2920K(258112K)] 1020134K->766622K(1044544K), 0.0549570 secs] [Times: user=0.19 sys=0.00, real=0.05 secs]



    可以发现Heap增长趋缓。



    运行两天以后输出:

    [GC [PSYoungGen: 256936K->3584K(257792K)] 859561K->606969K(1044224K), 0.0565910 secs] [Times: user=0.18 sys=0.01, real=0.06 secs]

    [GC [PSYoungGen: 256960K->3368K(257728K)] 860345K->607445K(1044160K), 0.0553780 secs] [Times: user=0.18 sys=0.01, real=0.06 secs]



    发现Heap反而减少了,此时可以对内存泄露问题作初步排除了。(其实在jdk1.6环境下用jprofiler来观察,对于concurrent那个内存泄露点的跟踪发现,内存的确还是会不断增长的,不过在一段时间后还是有回收,因此也就可以部分解释前面出现的情况)



    总结:

             对于GC输出的观察需要分两个维度来看。一个是纵向比较,也就是一次回收对于内存变化的观察。一个是横向比较,对于长时间内存分配占用情况的比较,这部分比较需要较长时间的观察,不能仅仅凭短时间的几个抽样比较,因为对于抽样来说,Full GC前后的区别,运行时长的区别,资源瞬时占用的区别都会影响判断。同时要结合Full GC发生的时间周期,每一次GC收集所耗费的时间作为辅助判断标准。

             顺便说一下,Heap的 YoungGen,OldGen,PermGen的设置也是需要注意的,并不是越大越好,越大执行收集的时间越久,但是可能执行Full GC的频率会比较低,因此需要权衡。这些仔细的去了解一下GC的基础设计思想会更有帮助,不过一般用默认的也不错。还有就是可以配置一些特殊的GC,并行,同步等等,充分利用多CPU的资源。

             对于GC的优化可以通过现在很多图形工具来做,也可以类似于我这样采用最原始的分析方式,好处就是任何时间任何地点只要知道原理就可以分析无需借助外部工具。原始的总是最好的^_^。

     

     

  • java jvm内存管理/gc策略/参数设置

    2011-07-29 11:49:20

    1. JVM内存管理:深入垃圾收集器与内存分配策略

    http://www.iteye.com/topic/802638

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。

    概述:

      说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项技术当做Java语言的伴生产物。事实上GC的历史远远比Java来得久远,在1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期,人们就在思考GC需要完成的3件事情:哪些内存需要回收?什么时候回收?怎么样回收?

      经过半个世纪的发展,目前的内存分配策略与垃圾回收技术已经相当成熟,一切看起来都进入“自动化”的时代,那为什么我们还要去了解GC和内存分配?答案很简单:当需要排查各种内存溢出、泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术有必要的监控、调节手段。

      把时间从1960年拨回现在,回到我们熟悉的Java语言。本文第一章中介绍了Java内存运行时区域的各个部分,其中程序计数器、VM栈、本地方法栈三个区域随线程而生,随线程而灭;栈中的帧随着方法进入、退出而有条不紊的进行着出栈入栈操作;每一个帧中分配多少内存基本上是在Class文件生成时就已知的(可能会由JIT动态晚期编译进行一些优化,但大体上可以认为是编译期可知的),因此这几个区域的内存分配和回收具备很高的确定性,因此在这几个区域不需要过多考虑回收的问题。而Java堆和方法区(包括运行时常量池)则不一样,我们必须等到程序实际运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,我们本文后续讨论中的“内存”分配与回收仅仅指这一部分内存。

    对象已死?

      在堆里面存放着Java世界中几乎所有的对象,在回收前首先要确定这些对象之中哪些还在存活,哪些已经“死去”了,即不可能再被任何途径使用的对象。

    引用计数算法(Reference Counting)

      最初的想法,也是很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,当有一个地方引用它,计数器加1,当引用失效,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的。

      客观的说,引用计数算法实现简单,判定效率很高,在大部分情况下它都是一个不错的算法,但引用计数算法无法解决对象循环引用的问题。举个简单的例子:对象A和B分别有字段b、a,令A.b=B和B.a=A,除此之外这2个对象再无任何引用,那实际上这2个对象已经不可能再被访问,但是引用计数算法却无法回收他们。

    根搜索算法(GC Roots Tracing)

      在实际生产的语言中(Java、C#、甚至包括前面提到的Lisp),都是使用根搜索算法判定对象是否存活。算法基本思路就是通过一系列的称为“GC Roots”的点作为起始进行向下搜索,当一个对象到GC Roots没有任何引用链(Reference Chain)相连,则证明此对象是不可用的。在Java语言中,GC Roots包括:

      1.在VM栈(帧中的本地变量)中的引用
      2.方法区中的静态引用
      3.JNI(即一般说的Native方法)中的引用

    生存还是死亡?

      判定一个对象死亡,至少经历两次标记过程:如果对象在进行根搜索后,发现没有与GC Roots相连接的引用链,那它将会被第一次标记,并在稍后执行他的finalize()方法(如果它有的话)。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这点是必须的,否则一个对象在finalize()方法执行缓慢,甚至有死循环什么的将会很容易导致整个系统崩溃。finalize()方法是对象最后一次逃脱死亡命运的机会,稍后GC将进行第二次规模稍小的标记,如果在finalize()中对象成功拯救自己(只要重新建立到GC Roots的连接即可,譬如把自己赋值到某个引用上),那在第二次标记时它将被移除出“即将回收”的集合,如果对象这时候还没有逃脱,那基本上它就真的离死不远了。

      需要特别说明的是,这里对finalize()方法的描述可能带点悲情的艺术加工,并不代表笔者鼓励大家去使用这个方法来拯救对象。相反,笔者建议大家尽量避免使用它,这个不是C/C++里面的析构函数,它运行代价高昂,不确定性大,无法保证各个对象的调用顺序。需要关闭外部资源之类的事情,基本上它能做的使用try-finally可以做的更好。

    关于方法区

      方法区即后文提到的永久代,很多人认为永久代是没有GC的,《Java虚拟机规范》中确实说过可以不要求虚拟机在这区实现GC,而且这区GC的“性价比”一般比较低:在堆中,尤其是在新生代,常规应用进行一次GC可以一般可以回收70%~95%的空间,而永久代的GC效率远小于此。虽然VM Spec不要求,但当前生产中的商业JVM都有实现永久代的GC,主要回收两部分内容:废弃常量与无用类。这两点回收思想与Java堆中的对象回收很类似,都是搜索是否存在引用,常量的相对很简单,与对象类似的判定即可。而类的回收则比较苛刻,需要满足下面3个条件:

      1.该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。
      2.加载该类的ClassLoader已经被GC。
      3.该类对应的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。

      是否对类进行回收可使用-XX:+ClassUnloading参数进行控制,还可以使用-verbose:class或者-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载、卸载信息。

      在大量使用反射、动态代理、CGLib等bytecode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要JVM具备类卸载的支持以保证永久代不会溢出。

    垃圾收集算法

      在这节里不打算大量讨论算法实现,只是简单的介绍一下基本思想以及发展过程。最基础的搜集算法是“标记-清除算法”(Mark-Sweep),如它的名字一样,算法分层“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象,整个过程其实前一节讲对象标记判定的时候已经基本介绍完了。说它是最基础的收集算法原因是后续的收集算法都是基于这种思路并优化其缺点得到的。它的主要缺点有两个,一是效率问题,标记和清理两个过程效率都不高,二是空间问题,标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集动作。

      为了解决效率问题,一种称为“复制”(Copying)的搜集算法出现,它将可用内存划分为两块,每次只使用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次过清理掉。这样使得每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,未免太高了一点。

      现在的商业虚拟机中都是用了这一种收集算法来回收新生代,IBM有专门研究表明新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的eden空间和2块较少的survivor空间,每次使用eden和其中一块survivor,当回收时将eden和survivor还存活的对象一次过拷贝到另外一块survivor空间上,然后清理掉eden和用过的survivor。Sun Hotspot虚拟机默认eden和survivor的大小比例是8:1,也就是每次只有10%的内存是“浪费”的。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有10%以内的对象存活,当survivor空间不够用时,需要依赖其他内存(譬如老年代)进行分配担保(Handle Promotion)。

      复制收集算法在对象存活率高的时候,效率有所下降。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。因此人们提出另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这端边界以外的内存。

      当前商业虚拟机的垃圾收集都是采用“分代收集”(Generational Collecting)算法,这种算法并没有什么新的思想出现,只是根据对象不同的存活周期将内存划分为几块。一般是把Java堆分作新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法,譬如新生代每次GC都有大批对象死去,只有少量存活,那就选用复制算法只需要付出少量存活对象的复制成本就可以完成收集。

    垃圾收集器

      垃圾收集器就是收集算法的具体实现,不同的虚拟机会提供不同的垃圾收集器。并且提供参数供用户根据自己的应用特点和要求组合各个年代所使用的收集器。本文讨论的收集器基于Sun Hotspot虚拟机1.6版。

    图1.Sun JVM1.6的垃圾收集器


      图1展示了1.6中提供的6种作用于不同年代的收集器,两个收集器之间存在连线的话就说明它们可以搭配使用。在介绍着些收集器之前,我们先明确一个观点:没有最好的收集器,也没有万能的收集器,只有最合适的收集器。

    1.Serial收集器
      单线程收集器,收集时会暂停所有工作线程(我们将这件事情称之为Stop The World,下称STW),使用复制收集算法,虚拟机运行在Client模式时的默认新生代收集器。

    2.ParNew收集器
      ParNew收集器就是Serial的多线程版本,除了使用多条收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial收集器一摸一样。对应的这种收集器是虚拟机运行在Server模式的默认新生代收集器,在单CPU的环境中,ParNew收集器并不会比Serial收集器有更好的效果。

    3.Parallel Scavenge收集器
      Parallel Scavenge收集器(下称PS收集器)也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。

    4.Serial Old收集器
      Serial Old是单线程收集器,使用标记-整理算法,是老年代的收集器,上面三种都是使用在新生代收集器。

    5.Parallel Old收集器
      老年代版本吞吐量优先收集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择,因为PS无法与CMS收集器配合工作。

    6.CMS(Concurrent Mark Sweep)收集器
      CMS是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,这一点对于实时或者高交互性应用(譬如证券交易)来说至关重要,这类应用对于长时间STW一般是不可容忍的。CMS收集器使用的是标记-清除算法,也就是说它在运行期间会产生空间碎片,所以虚拟机提供了参数开启CMS收集结束后再进行一次内存压缩。
    内存分配与回收策略

      了解GC其中很重要一点就是了解JVM的内存分配策略:即对象在哪里分配和对象什么时候回收。

      关于对象在哪里分配,往大方向讲,主要就在堆上分配,但也可能经过JIT进行逃逸分析后进行标量替换拆散为原子类型在栈上分配,也可能分配在DirectMemory中(详见本文第一章)。往细节处讲,对象主要分配在新生代eden上,也可能会直接老年代中,分配的细节决定于当前使用的垃圾收集器类型与VM相关参数设置。我们可以通过下面代码来验证一下Serial收集器(ParNew收集器的规则与之完全一致)的内存分配和回收的策略。读者看完Serial收集器的分析后,不妨自己根据JVM参数文档写一些程序去实践一下其它几种收集器的分配策略。

    清单1:内存分配测试代码

     
    1. public class YoungGenGC {   
    2.   
    3.     private static final int _1MB = 1024 * 1024;   
    4.   
    5.     public static void main(String[] args) {   
    6.         // testAllocation();   
    7.         testHandlePromotion();   
    8.         // testPretenureSizeThreshold();   
    9.         // testTenuringThreshold();   
    10.         // testTenuringThreshold2();   
    11.     }   
    12.   
    13.     /**  
    14.      * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8  
    15.      */  
    16.     @SuppressWarnings("unused")   
    17.     public static void testAllocation() {   
    18.         byte[] allocation1, allocation2, allocation3, allocation4;   
    19.         allocation1 = new byte[2 * _1MB];   
    20.         allocation2 = new byte[2 * _1MB];   
    21.         allocation3 = new byte[2 * _1MB];   
    22.         allocation4 = new byte[4 * _1MB];  // 出现一次Minor GC   
    23.     }   
    24.   
    25.     /**  
    26.      * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8  
    27.      * -XX:PretenureSizeThreshold=3145728  
    28.      */  
    29.     @SuppressWarnings("unused")   
    30.     public static void testPretenureSizeThreshold() {   
    31.         byte[] allocation;   
    32.         allocation = new byte[4 * _1MB];  //直接分配在老年代中   
    33.     }   
    34.   
    35.     /**  
    36.      * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1  
    37.      * -XX:+PrintTenuringDistribution  
    38.      */  
    39.     @SuppressWarnings("unused")   
    40.     public static void testTenuringThreshold() {   
    41.         byte[] allocation1, allocation2, allocation3;   
    42.         allocation1 = new byte[_1MB / 4];  // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置   
    43.         allocation2 = new byte[4 * _1MB];   
    44.         allocation3 = new byte[4 * _1MB];   
    45.         allocation3 = null;   
    46.         allocation3 = new byte[4 * _1MB];   
    47.     }   
    48.   
    49.     /**  
    50.      * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15  
    51.      * -XX:+PrintTenuringDistribution  
    52.      */  
    53.     @SuppressWarnings("unused")   
    54.     public static void testTenuringThreshold2() {   
    55.         byte[] allocation1, allocation2, allocation3, allocation4;   
    56.         allocation1 = new byte[_1MB / 4];   // allocation1+allocation2大于survivo空间一半   
    57.         allocation2 = new byte[_1MB / 4];     
    58.         allocation3 = new byte[4 * _1MB];   
    59.         allocation4 = new byte[4 * _1MB];   
    60.         allocation4 = null;   
    61.         allocation4 = new byte[4 * _1MB];   
    62.     }   
    63.   
    64.     /**  
    65.      * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure  
    66.      */  
    67.     @SuppressWarnings("unused")   
    68.     public static void testHandlePromotion() {   
    69.         byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;   
    70.         allocation1 = new byte[2 * _1MB];   
    71.         allocation2 = new byte[2 * _1MB];   
    72.         allocation3 = new byte[2 * _1MB];   
    73.         allocation1 = null;   
    74.         allocation4 = new byte[2 * _1MB];   
    75.         allocation5 = new byte[2 * _1MB];   
    76.         allocation6 = new byte[2 * _1MB];   
    77.         allocation4 = null;   
    78.         allocation5 = null;   
    79.         allocation6 = null;   
    80.         allocation7 = new byte[2 * _1MB];   
    81.     }   
    82. }  



    规则一:通常情况下,对象在eden中分配。当eden无法分配时,触发一次Minor GC。

      执行testAllocation()方法后输出了GC日志以及内存分配状况。-Xms20M -Xmx20M -Xmn10M这3个参数确定了Java堆大小为20M,不可扩展,其中10M分配给新生代,剩下的10M即为老年代。-XX:SurvivorRatio=8决定了新生代中eden与survivor的空间比例是1:8,从输出的结果也清晰的看到“eden space 8192K、from space 1024K、to space 1024K”的信息,新生代总可用空间为9216K(eden+1个survivor)。

      我们也注意到在执行testAllocation()时出现了一次Minor GC,GC的结果是新生代6651K变为148K,而总占用内存则几乎没有减少(因为几乎没有可回收的对象)。这次GC是发生的原因是为allocation4分配内存的时候,eden已经被占用了6M,剩余空间已不足分配allocation4所需的4M内存,因此发生Minor GC。GC期间虚拟机发现已有的3个2M大小的对象全部无法放入survivor空间(survivor空间只有1M大小),所以直接转移到老年代去。GC后4M的allocation4对象分配在eden中。

    清单2:testAllocation()方法输出结果

    [GC [DefNew: 6651K->148K(9216K), 0.0070106 secs] 6651K->6292K(19456K), 0.0070426 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    Heap
    def new generation   total 9216K, used 4326K [0x029d0000, 0x033d0000, 0x033d0000)
      eden space 8192K,  51% used [0x029d0000, 0x02de4828, 0x031d0000)
      from space 1024K,  14% used [0x032d0000, 0x032f5370, 0x033d0000)
      to   space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000)
    tenured generation   total 10240K, used 6144K [0x033d0000, 0x03dd0000, 0x03dd0000)
       the space 10240K,  60% used [0x033d0000, 0x039d0030, 0x039d0200, 0x03dd0000)
    compacting perm gen  total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)
       the space 12288K,  17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)
    No shared spaces configured.

    规则二:配置了PretenureSizeThreshold的情况下,对象大于设置值将直接在老年代分配。

      执行testPretenureSizeThreshold()方法后,我们看到eden空间几乎没有被使用,而老年代的10M控件被使用了40%,也就是4M的allocation对象直接就分配在老年代中,则是因为PretenureSizeThreshold被设置为3M,因此超过3M的对象都会直接从老年代分配。

    清单3:

    Heap
    def new generation   total 9216K, used 671K [0x029d0000, 0x033d0000, 0x033d0000)
      eden space 8192K,   8% used [0x029d0000, 0x02a77e98, 0x031d0000)
      from space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000)
      to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000)
    tenured generation   total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000)
       the space 10240K,  40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000)
    compacting perm gen  total 12288K, used 2107K [0x03dd0000, 0x049d0000, 0x07dd0000)
       the space 12288K,  17% used [0x03dd0000, 0x03fdefd0, 0x03fdf000, 0x049d0000)
    No shared spaces configured.

    规则三:在eden经过GC后存活,并且survivor能容纳的对象,将移动到survivor空间内,如果对象在survivor中继续熬过若干次回收(默认为15次)将会被移动到老年代中。回收次数由MaxTenuringThreshold设置。

      分别以-XX:MaxTenuringThreshold=1和-XX:MaxTenuringThreshold=15两种设置来执行testTenuringThreshold(),方法中allocation1对象需要256K内存,survivor空间可以容纳。当MaxTenuringThreshold=1时,allocation1对象在第二次GC发生时进入老年代,新生代已使用的内存GC后非常干净的变成0KB。而MaxTenuringThreshold=15时,第二次GC发生后,allocation1对象则还留在新生代survivor空间,这时候新生代仍然有404KB被占用。

    清单4:
    MaxTenuringThreshold=1

    [GC [DefNew
    Desired survivor size 524288 bytes, new threshold 1 (max 1)
    - age   1:     414664 bytes,     414664 total
    : 4859K->404K(9216K), 0.0065012 secs] 4859K->4500K(19456K), 0.0065283 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
    [GC [DefNew
    Desired survivor size 524288 bytes, new threshold 1 (max 1)
    : 4500K->0K(9216K), 0.0009253 secs] 8596K->4500K(19456K), 0.0009458 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    Heap
    def new generation   total 9216K, used 4178K [0x029d0000, 0x033d0000, 0x033d0000)
      eden space 8192K,  51% used [0x029d0000, 0x02de4828, 0x031d0000)
      from space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000)
      to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000)
    tenured generation   total 10240K, used 4500K [0x033d0000, 0x03dd0000, 0x03dd0000)
       the space 10240K,  43% used [0x033d0000, 0x03835348, 0x03835400, 0x03dd0000)
    compacting perm gen  total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)
       the space 12288K,  17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)
    No shared spaces configured.

    MaxTenuringThreshold=15
    [GC [DefNew
    Desired survivor size 524288 bytes, new threshold 15 (max 15)
    - age   1:     414664 bytes,     414664 total
    : 4859K->404K(9216K), 0.0049637 secs] 4859K->4500K(19456K), 0.0049932 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    [GC [DefNew
    Desired survivor size 524288 bytes, new threshold 15 (max 15)
    - age   2:     414520 bytes,     414520 total
    : 4500K->404K(9216K), 0.0008091 secs] 8596K->4500K(19456K), 0.0008305 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    Heap
    def new generation   total 9216K, used 4582K [0x029d0000, 0x033d0000, 0x033d0000)
      eden space 8192K,  51% used [0x029d0000, 0x02de4828, 0x031d0000)
      from space 1024K,  39% used [0x031d0000, 0x03235338, 0x032d0000)
      to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000)
    tenured generation   total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000)
       the space 10240K,  40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000)
    compacting perm gen  total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)
       the space 12288K,  17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)
    No shared spaces configured.

    规则四:如果在survivor空间中相同年龄所有对象大小的累计值大于survivor空间的一半,大于或等于个年龄的对象就可以直接进入老年代,无需达到MaxTenuringThreshold中要求的年龄。

      执行testTenuringThreshold2()方法,并将设置-XX:MaxTenuringThreshold=15,发现运行结果中survivor占用仍然为0%,而老年代比预期增加了6%,也就是说allocation1、allocation2对象都直接进入了老年代,而没有等待到15岁的临界年龄。因为这2个对象加起来已经到达了512K,并且它们是同年的,满足同年对象达到survivor空间的一半规则。我们只要注释掉其中一个对象new操作,就会发现另外一个就不会晋升到老年代中去了。

    清单5:
    [GC [DefNew
    Desired survivor size 524288 bytes, new threshold 1 (max 15)
    - age   1:     676824 bytes,     676824 total
    : 5115K->660K(9216K), 0.0050136 secs] 5115K->4756K(19456K), 0.0050443 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
    [GC [DefNew
    Desired survivor size 524288 bytes, new threshold 15 (max 15)
    : 4756K->0K(9216K), 0.0010571 secs] 8852K->4756K(19456K), 0.0011009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    Heap
    def new generation   total 9216K, used 4178K [0x029d0000, 0x033d0000, 0x033d0000)
      eden space 8192K,  51% used [0x029d0000, 0x02de4828, 0x031d0000)
      from space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000)
      to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000)
    tenured generation   total 10240K, used 4756K [0x033d0000, 0x03dd0000, 0x03dd0000)
       the space 10240K,  46% used [0x033d0000, 0x038753e8, 0x03875400, 0x03dd0000)
    compacting perm gen  total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)
       the space 12288K,  17% used [0x03dd0000, 0x03fe09a0, 0x03fe0a00, 0x049d0000)
    No shared spaces configured.

    规则五:在Minor GC触发时,会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间,如果大于,改为直接进行一次Full GC,如果小于则查看HandlePromotionFailure设置看看是否允许担保失败,如果允许,那仍然进行Minor GC,如果不允许,则也要改为进行一次Full GC。

      前面提到过,新生代才有复制收集算法,但为了内存利用率,只使用其中一个survivor空间来作为轮换备份,因此当出现大量对象在GC后仍然存活的情况(最极端就是GC后所有对象都存活),就需要老年代进行分配担保,把survivor无法容纳的对象直接放入老年代。与生活中贷款担保类似,老年代要进行这样的担保,前提就是老年代本身还有容纳这些对象的剩余空间,一共有多少对象在GC之前是无法明确知道的,所以取之前每一次GC晋升到老年代对象容量的平均值与老年代的剩余空间进行比较决定是否进行Full GC来让老年代腾出更多空间。

      取平均值进行比较其实仍然是一种动态概率的手段,也就是说如果某次Minor GC存活后的对象突增,大大高于平均值的话,依然会导致担保失败,这样就只好在失败后重新进行一次Full GC。虽然担保失败时做的绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure打开,避免Full GC过于频繁。

    清单6:
    HandlePromotionFailure = false

    [GC [DefNew: 6651K->148K(9216K), 0.0078936 secs] 6651K->4244K(19456K), 0.0079192 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
    [GC [DefNew: 6378K->6378K(9216K), 0.0000206 secs][Tenured: 4096K->4244K(10240K), 0.0042901 secs] 10474K->4244K(19456K), [Perm : 2104K->2104K(12288K)], 0.0043613 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]


    总结

      本章介绍了垃圾收集的算法、6款主要的垃圾收集器,以及通过代码实例具体介绍了新生代串行收集器对内存分配及回收的影响。

      GC在很多时候都是系统并发度的决定性因素,虚拟机之所以提供多种不同的收集器,提供大量的调节参数,是因为只有根据实际应用需求、实现方式选择最优的收集方式才能获取最好的性能。没有固定收集器、参数组合,也没有最优的调优方法,虚拟机也没有什么必然的行为。笔者看过一些文章,撇开具体场景去谈论老年代达到92%会触发Full GC(92%应当来自CMS收集器触发的默认临界点)、98%时间在进行垃圾收集系统会抛出OOM异常(98%应该来自parallel收集器收集时间比率的默认临界点)其实意义并不太大。因此学习GC如果要到实践调优阶段,必须了解每个具体收集器的行为、优势劣势、调节参数。

     

    2. 一次Java垃圾收集调优实战

    http://www.iteye.com/topic/212967

    1 资料

    • JDK5.0垃圾收集优化之--Don't Pause(花钱的年华)
    • 编写对GC友好,又不泄漏的代码(花钱的年华)
    • JVM调优总结
    • JDK 6所有选项及默认值

    2 GC日志打印

      GC调优是个很实验很伽利略的活儿,GC日志是先决的数据参考和最终验证:

    -XX:+PrintGCDetails -XX:+PrintGCTimeStamps(GC发生的时间) 
    -XX:+PrintGCApplicationStoppedTime(GC消耗了多少时间)
    -XX:+PrintGCApplicationConcurrentTime(GC之间运行了多少时间)

    3 收集器选择

    CMS收集器:暂停时间优先

       配置参数:-XX:+UseConcMarkSweepGC
       已默认无需配置的参数:-XX:+UseParNewGC(Parallel收集新生代) -XX:+CMSPermGenSweepingEnabled(CMS收集持久代) -XX:UseCMSCompactAtFullCollection(full gc时压缩年老代)

       初始效果:1g堆内存的新生代约60m,minor gc约5-20毫秒,full gc约130毫秒。

    Parallel收集器:吞吐量优先

        配置参数: -XX:+UseParallelGC -XX:+UseParallelOldGC(Parallel收集年老代,从JDK6.0开始支持)

        已默认无需配置的参数: -XX:+UseAdaptiveSizePolicy(动态调整新生代大小)

        初始效果:1g堆内存的新生代约90-110m(动态调整),minor gc约5-20毫秒,full gc有无UseParallelOldGC 参数分别为1.3/1.1秒,差别不大。

        另外-XX:MaxGCPauseMillis=100 设置minor gc的期望最大时间,JVM会以此来调整新生代的大小,但在此测试环境中对象死的太快,此参数作用不大。

    4 调优实战

          Parallel收集高达1秒的暂停时间基本不可忍受,所以选择CMS收集器。

          在被压测的Mule 2.0应用里,每秒都有大约400M的海量短命对象产生:

    1. 因为默认60M的新生代太小了,频繁发生minor gc,大约0.2秒就进行一次。
    2. 因为CMS收集器中MaxTenuringThreshold(生代对象撑过过多少次minor gc才进入年老代的设置)默认0,存活的临时对象不经过Survivor区直接进入年老代,不久就占满年老代发生full gc。

         对这两个参数的调优,既要改善上面两种情况,又要避免新生代过大,复制次数过多造成minor gc的暂停时间过长。

    1. 使用-Xmn调到1/3 总内存。观察后设置-Xmn500M,新生代实际约460m。(用-XX:NewRatio设置无效,只能用 -Xmn)。
    2. 添加-XX:+PrintTenuringDistribution 参数观察各个Age的对象总大小,观察后设置-XX:MaxTenuringThreshold=5。

          优化后,大约1.1秒才发生一次minor gc,且速度依然保持在15-20ms之间。同时年老代的增长速度大大减缓,很久才发生一次full gc,

          参数定稿:

     -server -Xms1024m -Xmx1024m -Xmn500m -XX:+UseConcMarkSweepGC   -XX:MaxTenuringThreshold=5  
    -XX:+ExplicitGCInvokesConcurrent

          最后服务处理速度从1180 tps 上升到1380 tps,调整两个参数提升17%的性能还是笔很划算的买卖。

     

         另外,JDK6 Update 7自带了一个VisualVM工具,内里就是之前也有用过的Netbean Profiler,类似JConsole一样使用,可以看到线程状态,内存中对象以及方法的CPU时间等调优重要参考依据。免费捆绑啊,Sun 这样搞法,其他做Profiler的公司要关门了。

    3. JVM gc参数设置与分析

    来自:http://hi.baidu.com/i1see1you/blog/item/7ba0d250c30131481038c20c.html

    一.概述

    java的最大好处是自动垃圾回收,这样就无需我们手动的释放对象空间了,但是也产生了相应的负效果,gc是需要时间和资源的,不好的gc会严重影响系统的系能,因此良好的gc是JVM的高性能的保证。JVM堆分为新生代,旧生代和年老代,新生代可用的gc方式有:串行gc(Serial Copying),并行回收gc(Parellel Scavenge),并行gc(ParNew),旧生代和年老代可用的gc方式有串行gc(Serial MSC),并行gc(Parallel MSC),并发gc(CMS)。

    二.回收方式的选择

    jvm有client和server两种模式,这两种模式的gc默认方式是不同的:

    clien模式下,新生代选择的是串行gc,旧生代选择的是串行gc

    server模式下,新生代选择的是并行回收gc,旧生代选择的是并行gc

    一般来说我们系统应用选择有两种方式:吞吐量优先和暂停时间优先,对于吞吐量优先的采用server默认的并行gc方式,对于暂停时间优先的选用并发gc(CMS)方式。

    三.CMS gc

    CMS,全称Concurrent Low Pause Collector,是jdk1.4后期版本开始引入的新gc算法,在jdk5和jdk6中得到了进一步改进,它的主要适合场景是对响应时间的重要性需求大于对吞吐量的要求,能够承受垃圾回收线程和应用线程共享处理器资源,并且应用中存在比较多的长生命周期的对象的应用。CMS是用于对tenured generation的回收,也就是年老代的回收,目标是尽量减少应用的暂停时间,减少full gc发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代。在我们的应用中,因为有缓存的存在,并且对于响应时间也有比较高的要求,因此希望能尝试使用CMS来替代默认的server型JVM使用的并行收集器,以便获得更短的垃圾回收的暂停时间,提高程序的响应性。
        CMS并非没有暂停,而是用两次短暂停来替代串行标记整理算法的长暂停,它的收集周期是这样:
        
    初始标记(CMS-initial-mark) -> 并发标记(CMS-concurrent-mark) -> 重新标记(CMS-remark) -> 并发清除(CMS-concurrent-sweep) ->并发重设状态等待下次CMS的触发(CMS-concurrent-reset)

        其中的1,3两个步骤需要暂停所有的应用程序线程的。第一次暂停从root对象开始标记存活的对象,这个阶段称为初始标记;第二次暂停是在并发标记之后,暂停所有应用程序线程,重新标记并发标记阶段遗漏的对象(在并发标记阶段结束后对象状态的更新导致)。第一次暂停会比较短,第二次暂停通常会比较长,并且 remark这个阶段可以并行标记。

        而并发标记、并发清除、并发重设阶段的所谓并发,是指一个或者多个垃圾回收线程和应用程序线程并发地运行,垃圾回收线程不会暂停应用程序的执行,如果你有多于一个处理器,那么并发收集线程将与应用线程在不同的处理器上运行,显然,这样的开销就是会降低应用的吞吐量。Remark阶段的并行,是指暂停了所有应用程序后,启动一定数目的垃圾回收进程进行并行标记,此时的应用线程是暂停的。

    四.full  gc

    full gc是对新生代,旧生代,以及持久代的统一回收,由于是对整个空间的回收,因此比较慢,系统中应当尽量减少full gc的次数。

    如下几种情况下会发生full gc:

    《旧生代空间不足

    《持久代空间不足

    《CMS GC时出现了promotion failed和concurrent mode failure

    《统计得到新生代minor gc时晋升到旧生代的平均大小小于旧生代剩余空间

    《直接调用System.gc,可以DisableExplicitGC来禁止

    《存在rmi调用时,默认会每分钟执行一次System.gc,可以通过-Dsun.rmi.dgc.server.gcInterval=3600000来设置大点的间隔。

    五.示例

    下面对如下的参数进行分析:

    JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4

    -verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log -Djava.awt.headless=true -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000

    -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"

    -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m

    Xms,即为jvm启动时得JVM初始堆大小,Xmx为jvm的最大堆大小,xmn为新生代的大小,permsize为永久代的初始大小,MaxPermSize为永久代的最大空间。

    -XX:SurvivorRatio=4

    SurvivorRatio为新生代空间中的Eden区和救助空间Survivor区的大小比值,默认是32,也就是说Eden区是 Survivor区的32倍大小,要注意Survivo是有两个区的,因此Surivivor其实占整个young genertation的1/34。调小这个参数将增大survivor区,让对象尽量在survitor区呆长一点,减少进入年老代的对象。去掉救助空间的想法是让大部分不能马上回收的数据尽快进入年老代,加快年老代的回收频率,减少年老代暴涨的可能性,这个是通过将-XX:SurvivorRatio 设置成比较大的值(比如65536)来做到。

     -verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log

    将虚拟机每次垃圾回收的信息写到日志文件中,文件名由file指定,文件格式是平文件,内容和-verbose:gc输出内容相同。

    -Djava.awt.headless=true

    Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。

    -XX:+PrintGCTimeStamps -XX:+PrintGCDetails

    设置gc日志的格式

    -Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000 

    指定rmi调用时gc的时间间隔

    -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15

    采用并发gc方式,经过15次minor gc 后进入年老代六.一些常见问题1.为了避免Perm区满引起的full gc,建议开启CMS回收Perm区选项:
    +CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled

    2.默认CMS是在tenured generation沾满68%的时候开始进行CMS收集,如果你的年老代增长不是那么快,并且希望降低CMS次数的话,可以适当调高此值:
    -XX:CMSInitiatingOccupancyFraction=80

    3.遇到两种fail引起full gc:Prommotion failed和Concurrent mode failed时:
    Prommot

  • java gc,java内存分析(性能测试总结之内存泄露和内存溢出)

    2011-07-28 19:10:51

     

    1. Java

    http://wenku.baidu.com/view/cb7becb765ce050876321307.html

     

    2.  性能测试总结之内存泄露和内存溢出

    http://www.uml.org.cn/Test/200912106.asp

    刚刚做完了一个项目的性能测试,“有幸”也遇到了内存泄露的案例,所以在此和大家分享一下。

    主要从以下几部分来说明,关于内存和内存泄露、溢出的概念,区分内存泄露和内存溢出;内存的区域划分,了解GC回收机制;重点关注如何去监控和发现内存问题;此外分析出问题还要如何解决内存问题。

    下面就开始本篇的内容:

    第一部分 概念

    众所周知,java中的内存java虚拟机自己去管理的,他不想C++需要自己去释放。笼统地去讲,java的内存分配分为两个部分,一个是数据堆,一个是栈。程序在运行的时候一般分配数据堆,把局部的临时的变量都放进去,生命周期和进程有关系。但是如果程序员声明了static的变量,就直接在栈中运行的,进程销毁了,不一定会销毁static变量。

    另外为了保证java内存不会溢出,java中有垃圾回收机制。 System.gc()即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。

    而其中,内存溢出就是你要求分配的java虚拟机内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

    内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问,该块已分配出来的内存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越多,系统也不能再次将它分配给需要的程序,产生泄露。一直下去,程序也逐渐无内存使用,就会溢出。

    第二部分 原理

    JAVA垃圾回收及对内存区划分

    在Java虚拟机规范中,提及了如下几种类型的内存空间:

    ◇ 栈内存(Stack):每个线程私有的。

    ◇ 堆内存(Heap):所有线程公用的。

    ◇ 方法区(Method Area):有点像以前常说的“进程代码段”,这里面存放了每个加载类的反射信息、类函数的代码、编译时常量等信息。

    ◇ 原生方法栈(Native Method Stack):主要用于JNI中的原生代码,平时很少涉及。

    而Java的使用的是堆内存,java堆是一个运行时数据区,类的实例(对象)从中分配空间。Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,“垃圾回收”也是主要是和堆内存(Heap)有关。

    垃圾回收的概念就是JAVA虚拟机(JVM)回收那些不再被引用的对象内存的过程。一般我们认为正在被引用的对象状态为“alive”,而没有被应用或者取不到引用属性的对象状态为“dead”。垃圾回收是一个释放处于”dead”状态的对象的内存的过程。而垃圾回收的规则和算法被动态的作用于应用运行当中,自动回收。

    JVM的垃圾回收器采用的是一种分代(generational )回收策略,用较高的频率对年轻的对象(young generation)进行扫描和回收,这种叫做minor collection,而对老对象(old generation)的检查回收频率要低很多,称为major collection。这样就不需要每次GC都将内存中所有对象都检查一遍,这种策略有利于实时观察和回收。

    (Sun JVM 1.3 有两种最基本的内存收集方式:一种称为copying或scavenge,将所有仍然生存的对象搬到另外一块内存后,整块内存就可回收。这种方法有效率,但需要有一定的空闲内存,拷贝也有开销。这种方法用于minor collection。另外一种称为mark-compact,将活着的对象标记出来,然后搬迁到一起连成大块的内存,其他内存就可以回收了。这种方法不需要占用额外的空间,但速度相对慢一些。这种方法用于major collection. )

    一些对象被创建出来只是拥有短暂的生命周期,比如 iterators 和本地变量。

    另外一些对象被创建是拥有很长的生命周期,比如 高持久化对象等。

    垃圾回收器的分代策略是把内存区划分为几个代,然后为每个代分配一到多个内存区块。当其中一个代用完了分配给他的内存后,JVM会在分配的内存区内执行一个局部的GC(也可以叫minor collection)操作,为了回收处于“dead”状态的对象所占用的内存。局部GC通常要不Full GC要快很多。

    JVM定义了两个代,年轻代(yong generation)(有时称为“nursery”托儿所)和老年代(old generation)。年轻代包括 “Eden space(伊甸园)”和两个“survivor spaces”。虚拟内存初始化的时候会把所有对象都分配到 Eden space,并且大部分对象也会在该区域被释放。 当进行  minor GC的时候,VM会把剩下的没有释放的对象从Eden space移动到其中一个survivor spaces当中。此外,VM也会把那些长期存活在survivor spaces 里的对象移动到 老生代的“tenured” space中。当 tenured generation 被填满后,就会产生Full GC,Full GC会相对比较慢因为回收的内容包括了所有的 live状态的对象。pemanet generation这个代包括了所有java虚拟机自身使用的相对比较稳定的数据对象,比如类和对象方法等。

    关于代的划分,可以从下图中获得一个概况:

    JVM

    如果垃圾回收器影响了系统的性能,或者成为系统的瓶颈,你可以通过自定义各个代的大小来优化它的性能。使用JConsole,可以方便的查看到当前应用所配置的垃圾回收器的各个参数。想要获得更详细的参数,可以参考以下调优介绍:

    Tuning Garbage collection with the 5.0 HotSpot VM

    http://java.sun.com/docs/hotspot/gc/index.html

    最后,总结一下各区内存:

    Eden Space (heap): 内存最初从这个线程池分配给大部分对象。

    Survivor Space (heap):用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。

    Tenured Generation (heap):用于保持已经在 survivor space内存池中存在了一段时间的对象。

    Permanent Generation (non-heap): 保存虚拟机自己的静态(refective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。这个区域被分割为只读的和只写的,

    Code Cache (non-heap):HotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache)

    第三部分 监控(工具发现问题)

    谈到内存监控工具,JConsole是必须要介绍的,它是一个用JAVA写的GUI程序,用来监控VM,并可监控远程的VM,易用且功能强大。具体可监控JAVA内存、JAVA CPU使用率、线程执行情况、加载类概况等,Jconsole需要在JVM参数中配置端口才能使用。

    由于是GUI程序,界面可视化,这里就不做详细介绍,

    具体帮助支持文档请参阅性能测试JConsole使用方法总结:

    http://www.taobao.ali.com/chanpin/km/test/DocLib/性能测试辅助工具-JConsole的使用方法.aspx

    或者参考SUN官网的技术文档:

    http://Java.sun.com/j2se/1.5.0/docs/guide/management/jconsole.html

    http://Java.sun.com/javase/6/docs/technotes/tools/share/jconsole.html

    在实际测试某一个项目时,内存出现泄露现象。起初在性能测试的1个小时中,并不明显,而在稳定性测试的时候才发现,应用的HSF调用在经过几个小时运行后,就出现性能明显下降的情况。在服务日志中报大量HSF超时,但所调用系统没有任何超时日志,并且压力应用的load都很低。经过查看日志后,认为应用可能存在内存泄漏。通过jconsole 以及 jmap 工具进行分析发现,确实存在内存泄漏问题,其中PS Old Gen最终达到占用 100%的占用。如图所示:

    内存泄露

    从上图可以看到,虽然每次Full GC,JVM内存会有部分回收,但回收并不彻底,不可回收的内存对象会越来越多,这样便会出现以上的一个趋势。在Full GC无法回收的对象越来越多时,最终已使用内存达到系统分配的内存最大值,系统最后无内存可分配,最终down机。

    第四部分 分析

    经过开发和架构师对应用的分析,查看此时内存队列,看哪个对象占用数据最多,再利用jmap命令,对线程数据分析,如下所示:

    num     #instances         #bytes  class name

    ———————————————-

    1:       9248056        665860032  com.taobao.matrix.mc.domain.**

    2:       9248031        295936992  com.taobao.matrix.**

    3:       9248068        147969088  java.util.**

    4:       1542111        37010664   java.util.Date

    前三个instances不断增加,指代的是同一个代码逻辑,异步分发的问题,堵塞消息,回收多次都无法回收成功。导致内存溢出。

    此外,对应用的性能单独做了压测,他的性能只能支撑到一半左右,故发送消息的TPS,应用肯定无法处理过来,导致消息堆积,而JAVA垃圾回收期认为这些都是有用的对象,导致内存堆积,直至系统崩溃。

    调优方法

    由于具体调优方法涉及到应用的配置信息,故在此暂不列出,可以参考性能测试小组发布的《性能测试调优宝典》

    第四部分 总结

    内存溢出主要是由于代码编写时对某些方法、类应用不合理,或者没有预估到临时对象会占用很大内存量,或者把过多的数据放入JVM缓存,或者性能压力大导致消息堆积而占用内存,以至于在性能测试时,生成庞大数量的临时对象,GC时没有做出有效回收甚至根本就不能回收,造成内存空间不足,内存溢出。

    如果编码之前,对内存使用量进行预估,对放在内存中的数据进行评估,保证有用的信息尽快释放,无用的信息能够被GC回收,这样在一定程度上是可以避免内存溢出问题的。

     

     

    3. java gc监视方法与工具

    http://wjason.iteye.com/blog/356193

    项目需要,将gc的监视方法,和gc log的分析工具整理一下,

    问题一  让jvm输出log

    打印jvm的gc log, 需要如下配置jvm启动参数:

    方法1:
    -Xloggc:D:/gc.log

    方法2
    -Xloggc:D:/gc.log -XX:+PrintGCTimeStamps -XX:-PrintGCDetails -XX:+UseConcMarkSweepGC
     

    这些jvm启动参数的意义参考这里:the Java application launcher

    问题二  分析jvm的log

    我现在用的是HPjmeter, 和HPjtune,收费的东西公司用不了, 没调查, 但目前这两个基本满足了我的需求.

    网上有这么一篇blog: 用HPjmeter分析jvm的gc日志和heap dump . 里面有各种工具的下载地址.

    整理如下:

    HPjmeter 下载:

    ftp://ftp.hp.com/pub/gsy/HPjmeter_3.0.01.jar
    ftp://ftp.hp.com/pub/gsy/hpjmeter_console_3.0.01.00_windows_setup.exe
    ftp://ftp.hp.com/pub/gsy/hpjmeter_console_3.0.01.00_linux_setup.sh

    HPjtune 下载:ftp://ftp.hp.com/pub/gsy (支持目录方式浏览。)

    这篇blog中还介绍了其他的一些gc分析工具,同时也给出了相应的下载地址, 分别是:

    SAP Memory Analyzer(Eclipse插件),  gcviewer,  scimark2 benchmark

     

     

    4.用HPjtune分析GC日志(一个实际案例的诊断过程)

    http://www.hashei.me/2009/07/use-hpjtune-to-analysis-gc-log.html

     

     

  • java时间操作函数汇总

    2011-07-28 17:10:04

     

    java时间操作函数汇总

    经常用到时间日期类,所以就将常用的日期方法和属性都归纳总结如下,方便大家查找
    1.计算某一月份的最大天数
    Calendar time=Calendar.getInstance();
    time.clear();
    time.set(Calendar.YEAR,year);
    time.set(Calendar.MONTH,i-1);//注意,Calendar对象默认一月为0
    int day=time.getActualMaximum(Calendar.DAY_OF_MONTH);//本月份的天数
    注:在使用set方法之前,最好先clear一下,否则很多信息会继承自系统当前时间
    2.Calendar和Date的转化
    (1) Calendar转化为Date
    Calendar cal=Calendar.getInstance();
    Date date=cal.getTime();
    (2) Date转化为Calendar
    Date date=new Date();
    Calendar cal=Calendar.getInstance();
    cal.setTime(date);
    3.格式化输出日期时间
    Date date=new Date();
    SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    System.out.println(df.format(date));
    4.计算一年中的第几星期
    (1)计算某一天是一年中的第几星期
    Calendar cal=Calendar.getInstance();
    cal.set(Calendar.YEAR, 2006);
    cal.set(Calendar.MONTH, 9);
    cal.set(Calendar.DAY_OF_MONTH, 3);
    int weekno=cal.get(Calendar.WEEK_OF_YEAR);
    (2)计算一年中的第几星期是几号
    SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");
    Calendar cal=Calendar.getInstance();
    cal.set(Calendar.YEAR, 2006);
    cal.set(Calendar.WEEK_OF_YEAR, 1);
    cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
    System.out.println(df.format(cal.getTime()));
    输出:
    2006-01-02
    5.add()和roll()的用法
    (1)add()方法
    SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");
    Calendar cal=Calendar.getInstance();
    cal.set(Calendar.YEAR, 2006);
    cal.set(Calendar.MONTH, 9);
    cal.set(Calendar.DAY_OF_MONTH, 3);
    cal.add(Calendar.DATE, -4);
    Date date=cal.getTime();
    System.out.println(df.format(date));
    cal.add(Calendar.DATE, 4);
    date=cal.getTime();
    System.out.println(df.format(date));
    输出:
    2006-08-30
    2006-10-03
    (2)roll方法
    cal.set(Calendar.YEAR, 2006);
    cal.set(Calendar.MONTH, 9);
    cal.set(Calendar.DAY_OF_MONTH, 3);
    cal.roll(Calendar.DATE, -4);
    date=cal.getTime();
    System.out.println(df.format(date));
    cal.roll(Calendar.DATE, 4);
    date=cal.getTime();
    System.out.println(df.format(date));
    输出:
    2006-10-29
    2006-10-03
    可见,roll()方法在本月内循环,一般使用add()方法;
    6.计算两个任意时间中间的间隔天数
    (1)传进Calendar对象
    /** *//**计算两个时间之间相隔天数
    * @param startday 开始时间
    * @param endday 结束时间
    * @return
    */
    public int getIntervalDays(Calendar startday,Calendar endday)...{
    //确保startday在endday之前
    if(startday.after(endday))...{
    Calendar cal=startday;
    startday=endday;
    endday=cal;
    }
    //分别得到两个时间的毫秒数
    long sl=startday.getTimeInMillis();
    long el=endday.getTimeInMillis();

    long ei=el-sl;
    //根据毫秒数计算间隔天数
    return (int)(ei/(1000*60*60*24));
    }
    (2)传进Date对象
    /** *//**计算两个时间之间相隔天数
    * @param startday 开始时间
    * @param endday 结束时间
    * @return
    */
    public int getIntervalDays(Date startday,Date endday)...{
    //确保startday在endday之前
    if(startday.after(endday))...{
    Date cal=startday;
    startday=endday;
    endday=cal;
    }
    //分别得到两个时间的毫秒数
    long sl=startday.getTime();
    long el=endday.getTime();

    long ei=el-sl;
    //根据毫秒数计算间隔天数
    return (int)(ei/(1000*60*60*24));
    }同理,可以用相同的方法计算出任意两个时间相隔的小时数,分钟数,秒钟数等
    注:以上方法是完全按时间计算,有时并不能令人满意,如:
    startday="2006-10-11 20:00:00"
    endday="2006-10-12 8:00:00"
    计算结果为0,但是我们也许相让计算结果变为1,此时可以用如下方法实现:
    在传参之前,先设定endday的时间,如:
    endday.set(Calendar.HOUR_OF_DAY, 23);
    endday.set(Calendar.MINUTE, 59);
    endday.set(Calendar.SECOND, 59);
    endday.set(Calendar.MILLISECOND, 59);
    这样再传进去startday,endday,则结果就如我们所愿了。不过,如果嫌以上方法麻烦,可以参考以下方法:
    (3)改进精确计算相隔天数的方法
    public int getDaysBetween (Calendar d1, Calendar d2) ...{
    if (d1.after(d2)) ...{ // swap dates so that d1 is start and d2 is end
    java.util.Calendar swap = d1;
    d1 = d2;
    d2 = swap;
    }
    int days = d2.get(Calendar.DAY_OF_YEAR) - d1.get(Calendar.DAY_OF_YEAR);
    int y2 = d2.get(Calendar.YEAR);
    if (d1.get(Calendar.YEAR) != y2) ...{
    d1 = (Calendar) d1.clone();
    do ...{
    days += d1.getActualMaximum(Calendar.DAY_OF_YEAR);//得到当年的实际天数
    d1.add(Calendar.YEAR, 1);
    } while (d1.get(Calendar.YEAR) != y2);
    }
    return days;
    }


    获取系统当前时间:
    public static String getSystemTime(){
    Date date=new Date();
    SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return df.format(date);
    }

    //字符串转化成时间类型(字符串可以是任意类型,只要和SimpleDateFormat中的格式一致即可)

    java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("M/dd/yyyy hh:mm:ss a",java.util.Locale.US);

    java.util.Date d = sdf.parse("5/13/2003 10:31:37 AM");


    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    String mDateTime1=formatter.format(d);


       //当前时间

       Calendar cal = Calendar.getInstance();

    // SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss G E D F w W a E F");

       String mDateTime=formatter.format(cal.getTime());



       //1年前日期

       java.util.Date myDate=new java.util.Date();

       long myTime=(myDate.getTime()/1000)-60*60*24*365;

       myDate.setTime(myTime*1000);

       String mDate=formatter.format(myDate);

      

       //明天日期

       myDate=new java.util.Date();

       myTime=(myDate.getTime()/1000)+60*60*24;

       myDate.setTime(myTime*1000);

       mDate=formatter.format(myDate);



    //两个时间之间的天数

       SimpleDateFormat myFormatter = new SimpleDateFormat("yyyy-MM-dd");

       java.util.Date date= myFormatter.parse("2003-05-1");

       java.util.Date mydate= myFormatter.parse("1899-12-30");

       long day=(date.getTime()-mydate.getTime())/(24*60*60*1000);





    //加半小时

    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    java.util.Date date1 = format.parse("2002-02-28 23:16:00");

    long Time=(date1.getTime()/1000)+60*30;

    date1.setTime(Time*1000);

    String mydate1=formatter.format(date1);







    //年月周求日期

    SimpleDateFormat formatter2 = new SimpleDateFormat("yyyy-MM F E");

    java.util.Date date2= formatter2.parse("2003-05 5 星期五");

    SimpleDateFormat formatter3 = new SimpleDateFormat("yyyy-MM-dd");

    String mydate2=formatter3.format(date2);



    //求是星期几

    mydate= myFormatter.parse("2001-1-1");

    SimpleDateFormat formatter4 = new SimpleDateFormat("E");

    String mydate3=formatter4.format(mydate);

    在 开发web应用中,针对不同的数据库日期类型,我们需要在我们的程序中对日期类型做各种不同的转换。若对应数据库数据是oracle的Date类型,即只 需要年月日的,可以选择使用java.sql.Date类型,若对应的是MSsqlserver数据库的DateTime类型,即需要年月日时分秒的,选 择java.sql.Timestamp类型
    你可以使用dateFormat定义时间日期的格式,转一个字符串即可

    package personal.jessica;
    import java.util.Date;
    import java.util.Calendar;
    import java.sql.Timestamp;
    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.Locale;
    class Datetest{
    /**
    *method 将字符串类型的日期转换为一个timestamp(时间戳记java.sql.Timestamp)
    *@param dateString 需要转换为timestamp的字符串
    *@return dataTime timestamp
    */
    public final static java.sql.Timestamp string2Time(String dateString)
    throws java.text.ParseException {
    DateFormat dateFormat;
    dateFormat = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss.SSS", Locale.ENGLISH);//设定格式
    //dateFormat = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss", Locale.ENGLISH);
    dateFormat.setLenient(false);
    java.util.Date timeDate = dateFormat.parse(dateString);//util类型
    java.sql.Timestamp dateTime = new java.sql.Timestamp(timeDate.getTime());//Timestamp类型,timeDate.getTime()返回一个long型
    return dateTime;
    }
    /**
    *method 将字符串类型的日期转换为一个Date(java.sql.Date)
    *@param dateString 需要转换为Date的字符串
    *@return dataTime Date
    */
    public final static java.sql.Date string2Date(String dateString)
    throws java.lang.Exception {
    DateFormat dateFormat;
    dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
    dateFormat.setLenient(false);
    java.util.Date timeDate = dateFormat.parse(dateString);//util类型
    java.sql.Date dateTime = new java.sql.Date(timeDate.getTime());//sql类型
    return dateTime;
    }

    public static void main(String[] args){
    Date da = new Date();
    //注意:这个地方da.getTime()得到的是一个long型的值
    System.out.println(da.getTime());

    //由日期date转换为timestamp

    //第一种方法:使用new Timestamp(long)
    Timestamp t = new Timestamp(new Date().getTime());
    System.out.println(t);

    //第二种方法:使用Timestamp(int year,int month,int date,int hour,int minute,int second,int nano)
    Timestamp tt = new Timestamp(Calendar.getInstance().get(
    Calendar.YEAR) - 1900, Calendar.getInstance().get(
    Calendar.MONTH), Calendar.getInstance().get(
    Calendar.DATE), Calendar.getInstance().get(
    Calendar.HOUR), Calendar.getInstance().get(
    Calendar.MINUTE), Calendar.getInstance().get(
    Calendar.SECOND), 0);
    System.out.println(tt);

    try {
    String sToDate = "2005-8-18";//用于转换成java.sql.Date的字符串
    String sToTimestamp = "2005-8-18 14:21:12.123";//用于转换成java.sql.Timestamp的字符串
    Date date1 = string2Date(sToDate);
    Timestamp date2 = string2Time(sToTimestamp);
    System.out.println("Date:"+date1.toString());//结果显示
    System.out.println("Timestamp:"+date2.toString());//结果显示
    }catch(Exception e) {
    e.printStackTrace();
    }
    }
    }

    下面是例子:

    package test;

    import java.text.DateFormat;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;

    import java.util.Calendar;
    import java.util.Date;
    import java.util.Hashtable;

    import javax.swing.JOptionPane;

     

    public class Test2{

     public static boolean isdate(String s){
      String a[]=s.split("-");
      boolean flg=true;
      if(!(Integer.parseInt(a[0])>=1950 && Integer.parseInt(a[0])<=2050)){
        flg=false;
      }
      return flg;
     }
     
     public static boolean checkDate(String s){  
            boolean ret = true;  
            try{  
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd");  
                ret = df.format(df.parse(s)).equals(s);  
            }  
            catch(ParseException e){  
                ret = false;  
            }  
            return ret;  
        }
     
     public Object dateinfo(String s){
      String a[]=s.split("-",2);
      Hashtable fest =new Hashtable();
      fest.put("01-01","元旦节");
      fest.put("02-14","情人节");
      fest.put("03-12","植树节");
      fest.put("03-15","消费者节");
      fest.put("04-01","愚人节");
      fest.put("04-05","清明节");
      fest.put("05-01","劳动节");
      fest.put("06-01","儿童节");
      fest.put("07-01","建党节");
      fest.put("08-01","建军节");
      fest.put("09-10","教师节");
      fest.put("10-01","国庆节");
      fest.put("12-25","圣诞节");
      if(fest.containsKey(a[1])){
      return fest.get(a[1]);
      }
      else{
      return "无节日";
      }
     }
     
     public String xingzuo(Date s){
      Calendar cal = Calendar.getInstance();
      cal.setTime(s);
      String xingzuo="无";
      int day=cal.get(Calendar.DAY_OF_YEAR);
      if((cal.get(Calendar.YEAR)%4==0)&&(cal.get(Calendar.YEAR)%100!=0)||(cal.get(Calendar.YEAR)%400==0)){
       if((day>=1 &&day<=19)||(day>=357&&day<=366)){
        xingzuo= "魔蝎座";
       }
       else if(day>=20 &&day<=49){
        xingzuo= "水瓶座";
       }
       else if(day>=50 &&day<=80){
        xingzuo= "双鱼座";
       }
       else if(day>=81 &&day<=110){
        xingzuo= "白羊座";
       }
       else if(day>=111 &&day<=141){
        xingzuo= "金牛座";
       }
       else if(day>=142 &&day<=173){
        xingzuo= "双子座";
       }
       else if(day>=174 &&day<=203){
        xingzuo= "巨蟹座";
       }
       else if(day>=204 &&day<=235){
        xingzuo= "狮子座";
       }
       else if(day>=236 &&day<=266){
        xingzuo= "处女座";
       }
       else if(day>=267 &&day<=296){
        xingzuo= "天秤座";
       }
       else if(day>=297 &&day<=326){
        xingzuo= "天蝎座";
       }
       else if(day>=327 &&day<=356){
        xingzuo= "射手座";
       }
      }
      else{
       if((day>=1 &&day<=19)||(day>=357&&day<=366)){
        xingzuo= "魔蝎座";
       }
       else if(day>=20 &&day<=48){
        xingzuo= "水瓶座";
       }
       else if(day>=49 &&day<=79){
        xingzuo= "双鱼座";
       }
       else if(day>=80 &&day<=109){
        xingzuo= "白羊座";
       }
       else if(day>=110 &&day<=140){
        xingzuo= "金牛座";
       }
       else if(day>=141 &&day<=172){
        xingzuo= "双子座";
       }
       else if(day>=173 &&day<=202){
        xingzuo= "巨蟹座";
       }
       else if(day>=203 &&day<=234){
        xingzuo= "狮子座";
       }
       else if(day>=235 &&day<=265){
        xingzuo= "处女座";
       }
       else if(day>=266 &&day<=295){
        xingzuo= "天秤座";
       }
       else if(day>=296 &&day<=325){
        xingzuo= "天蝎座";
       }
       else if(day>=326 &&day<=355){
        xingzuo= "射手座";
       }
      }
      return xingzuo;
     }
     
     public Date parseDate(String s){
      SimpleDateFormat bartDateFormat = new SimpleDateFormat("yyyy-MM-dd");
       try {
        Date date3 = bartDateFormat.parse(s);
        date3=bartDateFormat.parse(s);
        return date3;
       }
       catch (Exception ex) {
        return null;
       }
     }

     public static void main(String[] args) {
      Calendar cal = Calendar.getInstance();
      Test2 test2=new Test2();
      String date1=JOptionPane.showInputDialog("请你输入日期,格式如2000-10-15");
      
      while(!(Test2.checkDate(date1)&&Test2.isdate(date1))){
       date1=JOptionPane.showInputDialog("请你输入日期,格式如2000-10-15");
      }
       SimpleDateFormat bartDateFormat1 = new SimpleDateFormat("yyyy,MM,dd,EEEE");
       SimpleDateFormat bartDateFormat2 = new SimpleDateFormat("yyyy-MM-dd");
       Date date2=test2.parseDate(date1);
       String festinfo=(String)test2.dateinfo(date1);
       System.out.println(bartDateFormat1.format(date2) +","+ festinfo+","+ test2.xingzuo(date2));
      
       
       
       String day=JOptionPane.showInputDialog("请你输入要查询N天后的日期信息");
       cal.setTime(date2);
       cal.add(Calendar.DATE, Integer.parseInt(day));
       String date5=bartDateFormat2.format(cal.getTime());
       festinfo=(String)test2.dateinfo(date5);
       System.out.println(bartDateFormat1.format(cal.getTime())+","+ festinfo+","+ test2.xingzuo(cal.getTime())); 
     }
    }

     

     

    转自:http://www.iteye.com/topic/256420

     

     

  • java -D的参数

    2011-07-28 10:04:38

    -D   set a system property(设置系统属性)

    可通过语句System.getProperties().list(System.out);查看有哪些参数可以设置。

    可设置的参数:

    -- listing properties --
    java.runtime.name=Java(TM) 2 Runtime Environment, Stand...
    sun.boot.library.path=C:\Program Files\Java\jre1.5.0_08\bin
    java.vm.version=1.5.0_08-b03
    java.vm.vendor=Sun Microsystems Inc.
    java.vendor.url=http://java.sun.com/
    path.separator=;
    java.vm.name=Java HotSpot(TM) Client VM
    file.encoding.pkg=sun.io
    user.country=CN
    sun.os.patch.level=Service Pack 2
    java.vm.specification.name=Java Virtual Machine Specification
    user.dir=D:\wapSearchLogService
    java.runtime.version=1.5.0_08-b03
    java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
    java.endorsed.dirs=C:\Program Files\Java\jre1.5.0_08\lib...
    os.arch=x86
    java.io.tmpdir=C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\
    line.separator=

    java.vm.specification.vendor=Sun Microsystems Inc.
    user.variant=
    os.name=Windows XP
    sun.jnu.encoding=GBK
    java.library.path=C:\Program Files\Java\jre1.5.0_08\bin...
    java.specification.name=Java Platform. API Specification
    java.class.version=49.0
    sun.management.compiler=HotSpot Client Compiler
    os.version=5.1
    user.home=C:\Documents and Settings\Administrator
    user.timezone=Asia/Shanghai
    java.awt.printerjob=sun.awt.windows.WPrinterJob
    file.encoding=GBK
    java.specification.version=1.5
    user.name=Administrator
    java.class.path=D:\wapSearchLogService\bin;D:\wapSear...
    java.vm.specification.version=1.0
    sun.arch.data.model=32
    java.home=C:\Program Files\Java\jre1.5.0_08
    java.specification.vendor=Sun Microsystems Inc.
    user.language=zh
    awt.toolkit=sun.awt.windows.WToolkit
    java.vm.info=mixed mode, sharing
    java.version=1.5.0_08
    java.ext.dirs=C:\Program Files\Java\jre1.5.0_08\lib...
    sun.boot.class.path=C:\Program Files\Java\jre1.5.0_08\lib...
    java.vendor=Sun Microsystems Inc.
    file.separator=\
    java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport...
    sun.cpu.endian=little
    sun.io.unicode.encoding=UnicodeLittle
    sun.desktop=windows
    sun.cpu.isalist=

  • 正则表达式笔记及正则表达式30分钟入门教程

    2011-07-18 17:01:09

  • Java Service Wrapper使用说明-将java变为本地service

    2011-06-30 11:37:04

    Java Service Wrapper使用说明

     

     

    http://wrapper.tanukisoftware.com/doc/english/download.jsp

    http://www.oschina.net/p/java+service+wrapper

     

     

    具体的使用步骤:

    1.  将下载的Java Service Wrapper包解压到本地,目录为{WRAPPER_HOME}

    2.  服务应用程序名为[MyServApp],在目录C:\MyServApp下建立binconflogslib目录;并把你的已有应用程序如NioBlockingServer.class拷贝到该目录下;

    3.  {WRAPPER_HOME}\src\bin\下文件拷贝到MyServApp目录下,并重命名。

    {WRAPPER_HOME}\bin\Wrapper.exe -> C:\ MyServApp \bin\Wrapper.exe

    {WRAPPER_HOME}\src\bin\App.bat.in -> C:\ MyServApp\bin\MyApp.bat

    {WRAPPER_HOME}\src\bin\InstallApp-NT.bat.in ->C:\ MyServApp\bin\InstallMyApp-NT.bat

    {WRAPPER_HOME}\src\bin\UninstallApp-NT.bat.in -> C:\ MyServApp\bin\UninstallMyApp-NT.bat

    4.  {WRAPPER_HOME}\lib下的以下文件拷贝到C:\ MyServApp \lib目录下

    {WRAPPER_HOME}\lib\Wrapper.DLL

    {WRAPPER_HOME}\lib\wrapper.jar

    5.  {WRAPPER_HOME}\src\conf\wrapper.conf.in拷贝到C:\ MyServApp \conf目录下并命名为wrapper.conf;并修改wrapper.conf文件,在其中配置您的应用服务。

    主要修改以下几项即可:

    #你的JVM位置:

    wrapper.java.command=D:\Sun\j2sdk1.4.0_03\bin\java

    #运行参数:如:

    wrapper.java.additional.1=-Dprogram.name=run.bat

    #classpath

    wrapper.java.classpath.1=../lib/wrapper.jar

    wrapper.java.classpath.2=../bin/.

    # Java Library Path (location of Wrapper.DLL or libwrapper.so)

    wrapper.java.library.path.1=../lib

    #MAIN CLASS 此处决定了使用Java Service Wrapper的方式

    wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp

    #你的Java应用类

    wrapper.app.parameter.1= NonBlockingServer

    # 服务名

    wrapper.ntservice.name=NB

    # Display name of the service

    wrapper.ntservice.displayname=Nio Nonblocking Server

    # 服务描述

    wrapper.ntservice.description=Nio Nonblocking Server

    其他的配置根据你的需要改变即可

    6.  对以上配置的MyApp.bat进行测试,运行MyApp.bat,就像在Console窗口下运行Tomcat一样;

    7.  对以上配置的服务进行测试,运行C:\ MyServApp\bin\InstallMyApp-NT.bat将把你的应用(此处为NioBlockingServer)安装到Win32系统服务中了。

    8.  打开控制面板-管理程序-服务,看到Nio Nonblocking Server已经在系统服务中了,其他用法就与我们熟悉的Windows服务一样了。

     

    Tomcat使用的是Java Service Wrapper模式二,这种方式需要对已有的程序进行小的改动,但可以通过Socket端口的方式控制服务程序核心的启动,更加灵活。Java Service Wrapper提供的模式三比较复杂,需要作出更多的编码,我没有研究。

    采用模式一,即可简单有效的把我们的服务程序包装成为系统服务程序,并增强了日志功能,我们可以把MyServApp的几个文件做成模板,每次修改文件名,配置文件就可以了,有精力的朋友更可以做成Eclipseplugin,鼠标点点就把应用配成服务了。

  • 用java调用shell脚本,及解决阻塞的办法

    2011-05-16 14:18:22

    转自:http://tivan.iteye.com/blog/1045518

     

    用java调用shell,使用

    Process p=Runtime.getRuntime().exec(String[] cmd);

    Runtime.exec方法将产生一个本地的进程,并返回一个Process子类的实例,该实例可用于控制进程或取得进程的相关信息。
    由于调用Runtime.exec方法所创建的子进程没有自己的终端或控制台,因此该子进程的标准IO(如stdin,stdou,stderr)都通过
        p.getOutputStream(),
        p.getInputStream(),
        p.getErrorStream()
    方法重定向给它的父进程了.用户需要用这些stream来向 子进程输入数据或获取子进程的输出。
        
        例如:Runtime.getRuntime().exec("ls")


    另外需要关心的是Runtime.getRuntime().exec()中产生停滞(阻塞,blocking)的问题?


        这个是因为Runtime.getRuntime().exec()要自己去处理stdout和stderr的输出,
        就是说,执行的结果不知道是现有错误输出(stderr),还是现有标准输出(stdout)。
        你无法判断到底那个先输出,所以可能无法读取输出,而一直阻塞。
        例如:你先处理标准输出(stdout),但是处理的结果是先有错误输出(stderr),
        一直在等错误输出(stderr)被取走了,才到标准输出(stdout),这样就产生了阻塞。

    解决办法:


        用两个线程将标准输出(stdout)和错误输出(stderr)。

     

    参考代码:

     

    import java.util.*;
    import java.io.*;
    class StreamGobbler extends Thread
    {
        InputStream is;
        String type;
       
        StreamGobbler(InputStream is, String type)
        {
            this.is = is;
            this.type = type;
        }
       
        public void run()
        {
            try
            {
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                String line=null;
                while ( (line = br.readLine()) != null)
                    System.out.println(type + ">" + line);   
                } catch (IOException ioe)
                  {
                    ioe.printStackTrace(); 
                  }
        }
    }
    public class ExecRunner
    {
        public static void main(String args[])
        {
            if (args.length < 1)
            {
                System.out.println("USAGE: java GoodWindowsExec <cmd>");
                System.exit(1);
            }
           
            try
            {           
                String sName = System.getProperty("os.name" );
                String[] cmd = new String[3];
                if( osName.equals( "Windows NT" ) )
                {
                    cmd[0] = "cmd.exe" ;
                    cmd[1] = "/C" ;
                    cmd[2] = args[0];
                }
                else if( osName.equals( "Windows 95" ) )
                {
                    cmd[0] = "command.com" ;
                    cmd[1] = "/C" ;
                    cmd[2] = args[0];
                } else {
                 StringTokenizer st = new StringTokenizer(command, " ");
                 cmd = new String[st.countTokens()];
                 int token = 0;
                 while (st.hasMoreTokens()) {
                     String tokenString = st.nextToken();
                     // System.out.println(tokenString);
                     cmd[token++] = tokenString;
                 }
                }
               
                Runtime rt = Runtime.getRuntime();
                System.out.println("Execing " + cmd[0] + " " + cmd[1]
                                   + " " + cmd[2]);
                Process proc = rt.exec(cmd);
                // any error message?
                StreamGobbler errorGobbler = new
                    StreamGobbler(proc.getErrorStream(), "ERROR");           
               
                // any output?
                StreamGobbler utputGobbler = new
                    StreamGobbler(proc.getInputStream(), "OUTPUT");
                   
                // kick them off
                errorGobbler.start();
                outputGobbler.start();
                                       
                // any error???
                int exitVal = proc.waitFor();
                System.out.println("ExitValue: " + exitVal);       
            } catch (Throwable t)
              {
                t.printStackTrace();
              }
        }
    }

     

     

     

    参考文章:

     

    When Runtime.exec() won't

    Navigate yourself around pitfalls related to the Runtime.exec() method

     

    http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4

     

     

     

  • 07_memcached的运行状态监控

    2011-05-09 13:35:20

     

     

    memcached的运行状态监控
    http://jlsfwq.blog.51cto.com/818700/165628

    用cacti监控memcached的性能
    http://blogold.chinaunix.net/u/12479/showart_1160088.html


    监控宝:
    http://www.jiankongbao.com/

     

    用cacti监控memcached的性能

    源代码下载和安装配制方法见官方站点:http://dealnews.com/developers/cacti/memcached.html
    文中提到如果memcahed没有运行在默认的11211端口,可以修改Data Input Methods->Memcached - Statistics->Input String中加入-p 端口号,但是如果一台服务器运行了多个memcached,存在多个端口,这种方法就不行了,因为它只能传递一个端口号。
     
    经过测试,我想到了解决的方法。
    我的思路是:端口号由hostname来传递,做法是在添加设备的时候,在Hostname中加上-port,如:192.168.0.1-11011,然后在memcached.py脚本中从hostname分离出hostname和端口号,我修改后的源代码是:
    #!/usr/bin/python
    import sys
    import memcache
    from optparse import OptionParser
    stats = {'total_items': 0, 'bytes_written': 0, 'uptime': 0, 'bytes': 0,
             'cmd_get': 0, 'curr_items': 0, 'curr_connections': 0, 'connection_structures': 0,
             'limit_maxbytes': 0, 'rusage_user': 0.0, 'total_connections': 0, 'cmd_set': 0,
             'time': 0, 'get_misses': 0, 'bytes_read': 0, 'rusage_system': 0.0, 'get_hits': 0}
    parser = OptionParser(usage="usage: %prog [-h] [-p PORT] HOSTNAME1 HOSTNAME2 ...\nTotals will be returned for multiple HOSTNAME arguments.")
    parser.set_defaults(port = "11211")
    parser.add_option("-p", "--port", dest="port", metavar="PORT",
                      help="default memcached port [default: 11211]")
    (options, args) = parser.parse_args()
    hosts = []
    subStr = '-'
    if (args):
        for host in args:
            if subStr in host:
               host,port = host.split('-')
               hosts.append("%s:%s" % (host, port));
            else:

               hosts.append("%s:%s" % (host, options.port));
    else:
        parser.error("At least one HOSTNAME is required.")
        sys.exit(1)
    mc = memcache.Client(hosts, debug=0)
    mem_stats = mc.get_stats()
    if (not mem_stats):
            sys.exit()
    if (len(mem_stats) > 1):
        for mstat in mem_stats:
            for key, val in stats.iteritems():
                if (key == 'rusage_user' or key == 'rusage_system'):
                    stats[key] += float(mstat[1][key])
                else:
                    stats[key] += int(mstat[1][key])
                   
    else:
        mstat = mem_stats[0][1];
        for key, val in stats.iteritems():
            if (key == 'rusage_user' or key == 'rusage_system'):
                stats[key] = float(mstat[key])
            else:
                stats[key] = int(mstat[key])
               
    for stat, count in stats.iteritems():
        print "%s:%s" % (stat, count),
     
     
    其中红色为我加的代码,这样的话数据输入方法和图形模板都不用改
    如果是默认的11211端口,那么在cacti中Hostname正常添加就行了,不用加端口号。

     

     

     

     

     

  • 06_Memcached内存分析、调优、集群

    2011-05-09 13:22:19

  • PHP中的Java扩展

    2011-05-09 11:23:20

     

    http://www.chinaz.com/Program/PHP/0Z33B942008.html

    Java的易扩展性是它极其的令人兴奋的用途之一,通过学习如何使用这个模块,你可以扩展所有的可用到的Java类。为了使您了解到Java扩展性的基础,本篇文章包括系统的安装和一些PHP和Java一起使用的代码例子。

    Windows下安装

    以下的配置是基于Apache1.3.12,PHP4.0.3(您可以在www.php4win.de下载)和JDK1.2.2(您可以在java.sun.com下载)。我们已经在更老版本的JDK和Windows95,Windows98andNT4下的各种的MSwebservers(PWS和IIS)尝试过这种安装配置。

    第一步:安装JDK
    这个过程想当简单,由于JDK的安装没有过多的问题,但你需要做的是查看你的系统环境(在Windows9X中的autoexec.bat和NT环境下“控制面板”中的“系统”)并且确认JDK1.x.x\bin路径是否加载到path中。这个设置将会使你在编译Java类是变得更轻松。在Win9x中加入

    PATH=%PATH%;C:\jdk1.2.2\bin

    到你系统的autoexec.bat在NT的系统中加入

    ;C:\jdk1.2.2\bin

    到Path环境变量的末端。在系统中的autoexec.bat的标注是十分重要的。PHP的Java扩展将会忽JAVA_HOME和CLASSPATH在系统中的安装路径,这样一来,在php.ini中正确的设置这些项目就是十分的重要了。

    第二步:修改你的php.ini
    你需要加入如下的几项到你的php.ini文件

    [java]
    extension=php_java.dll
    java.home=C:\jdk1.2.2
    java.library=c:\jdk1.2.2\jre\bin\CLASSIC\jvm.dll
    java.library.path=c:\web\php4\extensions\
    java.class.path="c:\web\php4\extensions\jdk1.2.2\php_java.jar;c:\myclasses"

    具有代表性的问题是,有些人会把extension=php_java.dll与其它的extensions在php.ini文件中放在一起,但是它正确的位置应该是在php.ini文件中的[java]下面。java.library.path一定是在php_java.dll中被设置好的,并且java.class.path中一定要包含php_java.jar的路径。java.class.path还要加入其它你可能使用其它的类的路径,正如上面所举的例子中的c:\myclasses,或者你也可以只写前面的那段路径。

    第三步:测试你的安装成果
    现在,你可以建立一了类似于这样的一段php程序了:
    <?php

    $system=newJava("java.lang.System");
    print"Javaversion=".$system->getProperty("java.version")."<br>\n";
    print"Javavendor=".$system->getProperty("java.vendor")."<p>\n\n";
    print"OS=".$system->getProperty("os.name")."".
    $system->getProperty("os.version")."on".
    $system->getProperty("os.arch")."<br>\n";

    $formatter=newJava("java.text.SimpleDateFormat","EEEE,
    MMMMdd,yyyy'at'h:mm:ssazzzz");
    print$formatter->format(newJava("java.util.Date"))."\n";

    ?>
    这是一个SamRuby做的例子。如果你把没一项都设置好的话,你将会看到象这样的结果:
    Javaversion=1.2.2
    Javavendor=SunMicrosystemsInc.
    OS=Windows954.10onx86
    Wednesday,October18,2000at10:22:45AMChinaStandardTime

    (译者注:在本人的机器上运行的结果:
    Javaversion=1.2.2
    Javavendor=SunMicrosystemsInc.
    OS=Windows954.90onx86
    星期三,三月28,2001at1:44:33下午GMT+08:00)

    这是一个非常简单的例子,但它可以告诉你用php如何访问java类,一旦你能让这个例子转起来,就说明你的php的java扩展已经安装成功。

     

  • 05_Memcached 命令操作

    2011-05-09 10:48:27

     

    三篇文章:

    一、转 Memcached 命令操作
    http://af1200.blog.163.com/blog/static/1713227420108291133436/

    对于 Memcached 缓存系统的基本介绍、安装以及应用 之前有一编文章说过,下面主要是对使用Memcached系统的基本命令以及协议作个简单的分析,个人学习的笔记,呵呵。还希望能给需要的筒子一点参考:

    一、Memcache面向对象的常用接口包括:
    Memcache::connect -- 打开一个到Memcache的连接
    Memcache::pconnect -- 打开一个到Memcache的长连接
    Memcache::close -- 关闭一个Memcache的连接
    Memcache::set -- 保存数据到Memcache服务器上
    Memcache::get -- 提取一个保存在Memcache服务器上的数据
    Memcache::replace -- 替换一个已经存在Memcache服务器上的项目
    Memcache::delete -- 从Memcache服务器上删除一个保存的项目
    Memcache::flush -- 刷新所有Memcache服务器上保存的项目(类似于删除所有的保存的项目)
    Memcache::getStats -- 获取当前Memcache服务器运行的状态

    For More:
    http://cn.php.net/memcache

    二、查看系统的运行状态:

    stats
    pid               Process id of this server process (memcache服务器的进程ID)
    uptime            Number of seconds this server has been running (服务器已经运行的秒数)
    time              Current UNIX time according to the server (服务器当前的UNIX时间)
    version           Version string of this server (memcache版本)
    pointer_size      Current system pointer 当前操作系统的指针大小(32位系统一般是32bit)
    rusage_user       Accumulated user time for this process (该进程累计的用户时间(秒:微妙))
    rusage_system     Accumulated system time for this process (该进程累计的系统时间(秒:微妙))
    curr_items        Current number of items stored by the server (服务器当前存储的内容数量)
    total_items       Total number of items stored by this server ever since it started (服务器启动以来存储过的内容总数)
    bytes             Current number of bytes used by this server to store items (服务器当前存储内容所占用的字节数)
    curr_connections  Number of open connections (当前打开着的连接数量)
    total_connections Total number of connections opened since the server started running (服务器运行以来接受的连接总数)
    connection_structures Number of connection structures allocated by the server (服务器分配的连接结构的数量)
    cmd_get             Cumulative number of retrieval requests (get命令(获取)总请求次数)
    cmd_set             Cumulative number of storage requests (set命令(保存)总请求次数)
    get_hits            Number of keys that have been requested and found present (请求成功的总次数)
    get_misses          Number of items that have been requested and not found (请求失败的总次数)
    threads             Current number of thread (当前线程数)
    bytes_read          Total number of bytes read by this server from network (服务器从网络读取到的总字节数)
    bytes_written       Total number of bytes sent by this server to network (服务器向网络发送的总字节数)
    limit_maxbytes      Number of bytes this server is allowed to use for storage. (服务器在存储时被允许使用的字节总数)
    evictions           Number of valid items removed from cache to free memory for new items (为获取空闲内存而删除的items数(分配给memcache的空间用满后需要删除旧的items来得到空间分配给新的items))

    其中,最关注最多的几个参数:
    uptime:是memcached运行的秒数。
    cmd_get:是查询缓存的次数。
    cmd_get/uptime 结果是平均每秒请求缓存的次数——结果值越大,说明Memcached的利用率越高,站点的访问量大,如果太低,用文件系统缓存就可以了,根本不会体现出使用memcached的强大性能。
    cmd_set:是设置key=>value的次数。整个memcached是个大hash,用cmd_get没有找到的内容,就会调用一下cmd_set写进缓存里。
    get_hits:是缓存命中的次数。所谓的命中率 = get_hits/cmd_get * 100%。
    get_misses:是缓存未命中的次数。get_misses加上get_hits就等于cmd_get。
    stats:显示服务器信息、统计数据等
    stats reset:清空统计数据

    stats slabs:显示各个slab的信息,包括chunk的大小、数目、使用情况等
    stats items:显示各个slab中item的数目和存储时长(最后一次访问距离现在的秒数)
    quit:退出

    三、利用shell命令操作Memcached
    1、数据存储(key为wan,value为123)
    set
    2、数据取回
    get
    3、替换数据(将以wan为key存储的值替换为122)
    replace
    4、数值增加 1
    incr
    5、数值减少 2
    decr
    6、数据删除
    delete
    7、查看Memcached当时状态
    printf "stats\r\n" | nc 127.0.0.1 11211
    8、查看Memcached实时状态

    watch "printf 'stats\r\n' | nc 127.0.0.1 11211"

    watch

    Memcached protocol 中英文档可以参考:

    http://blog.s135.com/book/memcached/

    四. 查看slabs的使用状况
    使用memcached的创造着Brad写的名为 memcached-tool 的Perl脚本,可以方便地获得slab的使用情况(它将memcached的返回值整理成容易阅读的格式)。可以从下面的地址获得脚本:
    http://code.sixapart.com/svn/memcached/trunk/server/scripts/memcached-tool
    [root@localhost html]# vim memcached-tool
    [root@localhost html]# chmod +x memcached-tool
    [root@localhost html]# ./memcached-tool 127.0.0.1:11211
      #  Item_Size   Max_age  1MB_pages Count   Full?
      1      80 B        0 s               1           0      no
      2     104 B       12175 s         1           1      no
      3     176 B    1339587 s       33       196567  yes

    各列的含义:
    #: slab class编号
    Item_Size: Chunk大小
    Max_age: LRU内最旧的记录的生存时间
    1MB_pages: 分配给Slab的页数
    Count: Slab内的记录数
    Full?: Slab内是否含有空闲chunk

    五. 也可以图形化监控 Memcached 的运行状态
    http://livebookmark.net/journal/2008/05/21/memcachephp-stats-like-apcphp/
    是一个PHP源文件,只需要修改源码中的用户名、密码以及数组$MEMCACHE_SERVERS 就可以了。

     

     

     

    二、命令行查看Memcached运行状态
    http://blog.csdn.net/chenhongxin/archive/2010/11/11/6002764.aspx

    命令行查看Memcached运行状态

    很多时候需要监控服务器上的Memcached运行情况,比如缓存的查询次数,命中率之类的。但找到的
    那个memcached-tool是linux下用perl写的,我也没试过windows能不能用。后来发现个简单的办法
    可以做到,就是使用Telnet。
    首先登录到服务器,然后在cmd命令行中键入
    telnet 127.0.0.1 11211
    其中127.0.0.1是服务器的地址(这里是本机) ,11211是memcached绑定的端口号。
    之后命令行窗口全黑只有光标提示,摸黑输入stats,即可得到描述Memcached服务器运行情况的参
    数。如下图:
     
    其中,uptime 是memcached运行的秒数,cmd_get是查询缓存的次数。这两个数据相除一下就能得到
    平均每秒请求缓存的次数——最近niupu的流量很低,所以平均也就一秒请求一次多,这么点大的压
    力,用文件系统缓存一样没问题,根本不会体现出使用memcached的优越。
    下面的cmd_set 就是设置key=>value的次数。整个memcached是个大hash,用cmd_get没有找到的内
    容,就会调用一下cmd_set写进缓存里。紧跟着是get_hits,就是缓存命中的次数。缓存命中率 =
    get_hits/cmd_get * 100%。
    下面的get_misses的数字加上get_hits应该等于cmd_get。而total_itemscurr_items表示现在在缓
    存中的键值对个数,在图上total_items == cmd_set == get_misses,不过当可用最大内存用光时
    ,memcached就会删掉一些内容,上面的等式就不成立了。
    话说回来,memcached要是能有一套完整的监测工具就太好了。memcached的安装和php相应配置请看
    这里。


    Memcached的stats命令

     telnet到memcached服务器后有很多的命令可以使用,除了大家熟知的add、get、set、incr、decr、replace、delete等赋值命令外,还有一系列的获取服务器信息的命令,这部分命令都是以stats开头的。
    用PHP的Memcache::getStats($cmd)也可以访问这些命令

    常用的命令

    stats
    显示服务器信息、统计数据等

    stats reset
    清空统计数据

    stats malloc
    显示内存分配数据

    stats cachedump slab_id limit_num
    显示某个slab中的前limit_num个key列表,显示格式如下
    ITEM key_name [ value_length b; expire_time|access_time s]
    其中,memcached 1.2.2及以前版本显示的是  访问时间(timestamp)
    1.2.4以上版本,包括1.2.4显示 过期时间(timestamp)
    如果是永不过期的key,expire_time会显示为服务器启动的时间

    stats cachedump 7 2
    ITEM copy_test1 [250 b; 1207795754 s]
    ITEM copy_test [248 b; 1207793649 s]

    stats slabs
    显示各个slab的信息,包括chunk的大小、数目、使用情况等

    stats items
    显示各个slab中item的数目和最老item的年龄(最后一次访问距离现在的秒数)

    stats detail [on|off|dump]
    设置或者显示详细操作记录


    参数为on,打开详细操作记录
    参数为off,关闭详细操作记录
    参数为dump,显示详细操作记录(每一个键值get、set、hit、del的次数)

    stats detail dump
    PREFIX copy_test2 get 1 hit 1 set 0 del 0
    PREFIX copy_test1 get 1 hit 1 set 0 del 0
    PREFIX cpy get 1 hit 0 set 0 del 0


    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/chenhongxin/archive/2010/11/11/6002764.aspx

     

     

    三、memcached命令解析

    http://apps.hi.baidu.com/share/detail/24796149#

    memcached命令
      • 标准协议
        • No Reply
      • 存储命令
        • set命令
        • add命令
        • replace命令
        • append命令
        • prepend命令
        • cas命令
      • 读取命令
        • get命令
        • gets命令
      • 删除命令
      • incr/decr命令
      • 查看memcached使用状态
        • stats命令
        • stats items命令
        • stats slabs命令
        • stats sizes命令
      • flush_all命令

    全部协议在Protocol Documentation



    标准协议

    memcached所有的标准协议包含在对item执行命令过程中,一个item包含:

    • 一个key
    • 一个32位的标志值
    • 以秒为单位的失效时间
    • 一个64为的CAS值,这个是唯一的
    • 数据

    CAS是可选的,可以使用“

    -C

    ”禁止CAS。

    No Reply

    大多数ascii命令允许“noreply”。建议大家在ascii协议下别用“noreply”,因为那样不会报请求错误。“noreply”的目的是在执行交互命令(如:set、add)后,避免等待返回的包。

    二进制协议将“noreply”定义为声明。如果你的客户端支持或者使用二进制协议,那么你将会用到它。


    存储命令

    首先客户端向服务器按照如下格式发送命令行:

    <command name> <key> <flags> <exptime> <bytes>\r\n


        a) <command name> 可以是"set", "add", "replace"。

            "set"表示按照相应的<key>存储该数据。

            "add"表示按照相应的<key>添加该数据,但是如果该<key>已经存在则会操作失败。

            "replace"表示按照相应的<key>替换数据,但是如果该<key>不存在则操作失败


        b) <key> 客户端需要保存数据的key。


        c) <flags> 是一个16位的无符号的整数(以十进制的方式表示)。该标志将和需要存储的数据一起存储,并在客户端get数据时返回。客户可以将此标志用做特殊用途,此标志对服务器来说是不透明的。


        d) <exptime> 过期的时间。如果该数值为0表示存储的数据永远不过时(但是,该数据有可能被其他项所替换掉。因为服务器采用了LRU(最近最久没有使用)的算法替换)。 如果非0(unix时间或者距离此时的秒数),当过期后,服务器可以保证用户得不到该数据(以服务器时间为标准)。


        e) <bytes> 需要存储的字节数(不包含最后的"\r\n"),当用户希望存储空数据时,<bytes>可以为0


        f) 最后客户端需要加上"\r\n"作为"命令头"的结束标志。


    <data block>\r\n

        紧接着"命令头"结束之后就要发送数据块(即希望存储的数据内容),最后加上"\r\n"作为此次通讯的结束。


    reply

        当以上数据发送结束之后,服务器将返回一个应答。可能有如下的情况:


        a) "STORED\r\n"

            表示存储成功


        b) "NOT_STORED\r\n"

            表示存储失败,但是该失败不是由于错误。通常这是由于"add"或者"replace"命令本身的要求所引起的,或者该项在删除队列之中(见delete命令)。


    set

    set是保存数据命令。会覆盖已存在的数据,而新数据将在LRU顶端

    add

    只有在该数据不存在时才保存该数据。如果是新加入的item,那么将其直接放在LRU顶端;如果item已经存在导致add失败,那么将这个item从LRU链表上摘下再放到LRU顶端。

    replace

    替换已经存在的数据。 这个操作几乎用不到。

    append

    紧接着已经存在的item增加item。这个操作不允许增加原来的item限制,对管理链表很有用。

    prepend

    与append命令类似,这个命令是在已存在的数据前加入新数据。

    cas

    检查并存储(

    Check And Set)或者比较并更新(Compare And Swap)。如果从上次读取到现在没有更新,那么存入数据,处理更新竞争很有用。

    读取命令

    获取数据的格式: 

    get <key>*\r\n

        a) <key>* 表示一个或者多个key(以空格分开)

        b) "\r\n" 命令头的结束


    reply

        服务器端将返回0个或者多个的数据项。每个数据项都是由一个文本行和一个数据块组成。当所有的数据项都接收完毕将收到"END\r\n"


    每一项的数据结构:

    VALUE <key> <flags> <bytes>\r\n

    <data block>\r\n

        a) <key> 希望得到存储数据的key

        b) <falg> 发送set命令时设置的标志项

        c) <bytes> 发送数据块的长度(不包含"\r\n")

        d) "\r\n" 文本行的结束标志

        e) <data block> 希望接收的数据项。

        f) "\r\n" 接收一个数据项的结束标志。


    如果有些key出现在get命令行中但是没有返回相应的数据,这意味着服务器中不存在这些项,这些项过时了,或者被删除了。

    get

    读取命令, 更具一个或多个key查找数据,并返回所找到的数据。

    gets

    使用CAS的get命令,返回的item带有一个CAS标识符 (一个唯一的64位数)。使用cas命令返回数据。如果得到的item的cas值被更改了,这个数据将不会被保存。


    删除命令

    如果存在,将item从cache中删除,

    delete 命令格式: 

    delete <key> <time>\r\n

        a) <key> 需要被删除数据的key

        b) <time> 客户端希望服务器将该数据删除的时间(unix时间或者从现在开始的秒数)

        c) "\r\n" 命令头的结束


    reply


        a) "DELETED\r\n" 删除成功

        b) "NOT_FOUND\r\n" 需要删除的key不存在


    incr/decr

    Increment and Decrement. 如果item是以64为整型存储的,那么可以使用incr和decr命令修改那个数。

    如果数据不存在,那么将返回失败。

    命令格式:

    incr <key> <value>\r\n

    or

    decr <key> <value>\r\n

        a) <key> 数据项的key

        b) <value> 用户希望增加/减少的数据的数值.该数值是一个32位十进制的无符号整形变量。

        c) "\r\n" 命令行结束标志


    reply

        a) "NOT_FOUND\r\n" 没有找到需要操作的项。

        b) "<value>\r\n" <value>数据项有效期的最新剩余时间。


    注意: 

        a) 如果一个数据项的有效期被设置为0,这时使用decr命令是无法减少数据。

        b) 如果要执行 incr key -1 的操作不会有什么问题,结果和你希望的一样。但是,执行decr -1时的结果一定会让你觉得很意外,因为它的结果无论key的数据是什么结果的都是0.原因是:在这两个命令的执行过程中都是吧-1当做一个无符号的整形 处理的。

        c) 执行decr命令时数据的长度不会随之而减小,而是在返回数据的后面填补空格。但是执行incr命令越界后会自动的增加数据的位数。


    查看memcached使用状态

     通过这些命令可以查看memcached服务器的使用状态。

    stats

    查看memcached状态的基本命令,通过这个命令可以看到如下信息:

    STAT pid 22459                             进程ID
    STAT uptime 1027046                        服务器运行秒数
    STAT time 1273043062                       服务器当前unix时间戳
    STAT version 1.4.4                         服务器版本
    STAT pointer_size 64                       操作系统字大小(这台服务器是64位的)
    STAT rusage_user 0.040000                  进程累计用户时间
    STAT rusage_system 0.260000                进程累计系统时间
    STAT curr_connections 10                   当前打开连接数
    STAT total_connections 82                  曾打开的连接总数
    STAT connection_structures 13              服务器分配的连接结构数
    STAT cmd_get 54                            执行get命令总数
    STAT cmd_set 34                            执行set命令总数
    STAT cmd_flush 3                           指向flush_all命令总数
    STAT get_hits 9                            get命中次数
    STAT get_misses 45                         get未命中次数
    STAT delete_misses 5                       delete未命中次数
    STAT delete_hits 1                         delete命中次数
    STAT incr_misses 0                         incr未命中次数
    STAT incr_hits 0                           incr命中次数
    STAT decr_misses 0                         decr未命中次数
    STAT decr_hits 0                           decr命中次数
    STAT cas_misses 0    cas未命中次数
    STAT cas_hits 0                            cas命中次数
    STAT cas_badval 0                          使用擦拭次数
    STAT auth_cmds 0
    STAT auth_errors 0
    STAT bytes_read 15785                      读取字节总数
    STAT bytes_written 15222                   写入字节总数
    STAT limit_maxbytes 1048576                分配的内存数(字节)
    STAT accepting_conns 1                     目前接受的链接数
    STAT listen_disabled_num 0                 
    STAT threads 4                             线程数
    STAT conn_yields 0
    STAT bytes 0                               存储item字节数
    STAT curr_items 0                          item个数
    STAT total_items 34                        item总数
    STAT evictions 0                           为获取空间删除item的总数


    来自: http://hi.baidu.com/notesjava/blog/item/c82e52599b6eb4d09c8204b3.html


     

  • java性能监控

    2011-05-08 00:23:50

    http://www.ibm.com/developerworks/cn/java/j-5things7.html
    http://www.ibm.com/developerworks/cn/java/j-5things8.html

    全功能内置分析器,如 JConsole 和 VisualVM 的成本有时比它们的性能费用还要高 — 尤其是在生产软件上运行的系统中。因此,在聚焦 Java 性能监控的第 2 篇文章中,我将介绍 5 个命令行分析工具,使开发人员仅关注运行的 Java 进程的一个方面。

    JDK 包括很多命令行实用程序,可以用于监控和管理 Java 应用程序性能。虽然大多数这类应用程序都被标注为 “实验型”,在技术上不受支持,但是它们很有用。有些甚至是特定用途工具的种子材料,可以使用 JVMTI 或 JDI(参见 参考资料)建立。

    1. jps (sun.tools.jps)

    很多命令行工具都要求您识别您希望监控的 Java 进程。这与监控本地操作系统进程、同样需要一个程序识别器的同类工具没有太大区别。

    “VMID” 识别器与本地操作系统进程识别器(“pid”)并不总是相同的,这就是我们需要 JDK jps 实用程序的原因。
    在 Java 进程中使用 jps

    与配置 JDK 的大部分工具及本文中提及的所有工具一样,可执行 jps 通常是一个围绕 Java 类或执行大多数工作的类集的一个薄包装。在 Windows?? 环境下,这些工具是 .exe 文件,使用 JNI Invocation API 直接调用上面提及的类;在 UNIX?? 环境下,大多数工具是一个 shell 脚本的符号链接,该脚本采用指定的正确类名称开始一个普通启动程序。

    如果您希望在 Java 进程中使用 jps(或者任何其他工具)的功能 — Ant 脚本 — 仅在每个工具的 “主” 类上调用 main() 相对容易。为了简化引用,类名称出现在每个工具名称之后的括号内。

    jps — 名称反映了在大多数 UNIX 系统上发现的 ps 实用程序 — 告诉我们运行 Java 应用程序的 JVMID。顾名思义,jps 返回指定机器上运行的所有已发现的 Java 进程的 VMID。如果 jps 没有发现进程,并不意味着无法附加或研究 Java 进程,而只是意味着它并未宣传自己的可用性。

    如果发现 Java 进程,jps 将列出启用它的命令行。这种区分 Java 进程的方法非常重要,因为只要涉及操作系统,所有的 Java 进程都被统称为 “java”。在大多数情况下,VMID 是值得注意的重要数字。

    使用分析器开始

    使用分析实用程序开始的最简单方法是使用一个如在 demo/jfc/SwingSet2 中发现的 SwingSet2 演示一样的演示程序。这样就可以避免程序作为背景/监控程序运行时出现挂起的可能性。当您了解工具及其费用后,就可以在实际程序中进行试用。

    加载演示应用程序后,运行 jps 并注意返回的 vmid。为了获得更好的效果,采用 -Dcom.sun.management.jmxremote 属性集启动 Java 进程。如果没有使用该设置,部分下列工具收集的部分数据可能不可用。

    2. jstat (sun.tools.jstat)

    jstat 实用程序可以用于收集各种各样不同的统计数据。jstat 统计数据被分类到 “选项” 中,这些选项在命令行中被指定作为第一参数。对于 JDK 1.6 来说,您可以通过采用命令 -options 运行 jstat 查看可用的选项清单。清单 1 中显示了部分选项:


    清单 1. jstat 选项

                   
    -class
    -compiler
    -gc
    -gccapacity
    -gccause
    -gcnew
    -gcnewcapacity
    -gcold
    -gcoldcapacity
    -gcpermcapacity
    -gcutil
    -printcompilation


    实用程序的 JDK 记录(参见 参考资料)将告诉您清单 1 中每个选项返回的内容,但是其中大多数用于收集垃圾的收集器或者其部件的性能信息。-class 选项显示了加载及未加载的类(使其成为检测应用程序服务器或代码中 ClassLoader 泄露的重要实用程序,且 -compiler 和 -printcompilation 都显示了有关 Hotspot JIT 编译程序的信息。

    默认情况下,jstat 在您核对信息时显示信息。如果您希望每隔一定时间拍摄快照,请在 -options 指令后以毫秒为单位指定间隔时间。jstat 将持续显示监控进程信息的快照。如果您希望 jstat 在终止前进行特定数量的快照,在间隔时间/时间值后指定该数字。

    如果 5756 是几分钟前开始的运行 SwingSet2 程序的 VMID,那么下列命令将告诉 jstat 每 250 毫秒为 10 个佚代执行一次 gc 快照转储,然后停止:

    jstat -gc 5756 250 10


    请注意 Sun(现在的 Oracle)保留了在不进行任何预先通知的情况下更改各种选项的输出甚至是选项本身的权利。这是使用不受支持实用程序的缺点。请参看 Javadocs 了解 jstat 输出中每一列的全部细节。

    3. jstack (sun.tools.jstack)

    了解 Java 进程及其对应的执行线程内部发生的情况是一种常见的诊断挑战。例如,当一个应用程序突然停止进程时,很明显出现了资源耗尽,但是仅通过查看代码无法明确知道何处出现资源耗尽,且为什么会发生。

    jstack 是一个可以返回在应用程序上运行的各种各样线程的一个完整转储的实用程序,您可以使用它查明问题。

    采用期望进程的 VMID 运行 jstack 会产生一个堆转储。就这一点而言,jstack 与在控制台窗口内按 Ctrl-Break 键起同样的作用,在控制台窗口中,Java 进程正在运行或调用 VM 内每个 Thread 对象上的 Thread.getAllStackTraces() 或 Thread.dumpStack()。jstack 调用也转储关于在 VM 内运行的非 Java 线程的信息,这些线程作为 Thread 对象并不总是可用的。

    jstack 的 -l 参数提供了一个较长的转储,包括关于每个 Java 线程持有锁的更多详细信息,因此发现(和 squash)死锁或可伸缩性 bug 是极其重要的。

    4. jmap (sun.tools.jmap)

    有时,您正在处理的问题是一个对象泄露,如一个 ArrayList (可能持有成千上万个对象)该释放时没有释放。另一个更普遍的问题是,看似从不会压缩的扩展堆,却有活跃的垃圾收集。

    当您努力寻找一个对象泄露时,在指定时刻对堆及时进行拍照,然后审查其中内容非常有用。jmap 通过对堆拍摄快照来提供该功能的第一部分。然后您可以采用下一部分中描述的 jhat 实用程序分析堆数据。

    与这里描述的其他所有实用程序一样,使用 jmap 非常简单。将 jmap 指向您希望拍快照的 Java 进程的 VMID,然后给予它部分参数,用来描述产生的结果文件。您要传递给 jmap 的选项包括转储文件的名称以及是否使用一个文本文件或二进制文件。二进制文件是最有用的选项,但是只有当与某一种索引工具 结合使用时 — 通过十六进制值的文本手动操作数百兆字节不是最好的方法。

    随意看一下 Java 堆的更多信息,jmap 同样支持 -histo 选项。-histo 产生一个对象文本柱状图,现在在堆中大量引用,由特定类型消耗的字节总数分类。它同样给出了特定类型的总示例数量,支持部分原始计算,并猜测每个实例的相对成本。

    不幸的是,jmap 没有像 jstat 一样的 period-and-max-count 选项,但是将 jmap(或 jmap.main())调用放入 shell 脚本或其他类的循环,周期性地拍摄快照相对简单。(事实上,这是加入 jmap 的一个好的扩展,不管是作为 OpenJDK 本身的源补丁,还是作为其他实用程序的扩展。)

    5. jhat (com.sun.tools.hat.Main)

    将堆转储至一个二进制文件后,您就可以使用 jhat 分析二进制堆转储文件。jhat 创建一个 HTTP/HTML 服务器,该服务器可以在浏览器中被浏览,提供一个关于堆的 object-by-object 视图,及时冻结。根据对象引用草率处理堆可能会非常可笑,您可以通过对总体混乱进行某种自动分析而获得更好的服务。幸运的是,jhat 支持 OQL 语法进行这样的分析。

    例如,对所有含有超过 100 个字符的 String 运行 OQL 查询看起来如下:

    select s from java.lang.String s where s.count >= 100


    结果作为对象链接显示,然后展示该对象的完整内容,字段引用作为可以解除引用的其他链接的其他对象。OQL 查询同样可以调用对象的方法,将正则表达式作为查询的一部分,并使用内置查询工具。一种查询工具,referrers() 函数,显示了引用指定类型对象的所有引用。下面是寻找所有参考 File 对象的查询:

    select referrers(f) from java.io.File f


    您可以查找 OQL 的完整语法及其在 jhat 浏览器环境内 “OQL Help” 页面上的特性。将 jhat 与 OQL 相结合是对行为不当的堆进行对象调查的有效方法。

    结束语

    当您需要近距离观察 Java 进程内发生的事情时,JDK 的分析扩展会非常有用。本文中介绍的所有工具都可以从命令行中由其自己使用。它们还可以与 JConsole 或 VisualVM 有力地结合使用。JConsole 和 VisualVM 提供 Java 虚拟机的总体视图,jstat 和 jmap 等有针对性的工具支持您对研究进行微调。

     

     

    http://java.dzone.com/articles/java-performance-troubleshooti
    Top 10 Java Performance Troubleshooting Tools



     

    Here are 10 tools useful for Java application performance troubleshooting.


        * jconsole comes together with JDK 1.5 and above. It is a Java Monitoring and Management Console - JMX-compliant graphical tool for monitoring a Java virtual machine. It can monitor both local and remote JVMs.


        * VisualVM is a visual tool that integrates several existing JDK software tools and lightweight memory and CPU profiling capabilities. This tool is designed for both production and development time use and further enhances the capability of monitoring and performance analysis for the Java SE platform.


        * HeapAnalyzer allows the finding of a possible Java?? heap leak area through its heuristic search engine and analysis of the JavaTM heap dump in Java applications. It analyzes Java heap dumps by parsing the Java heap dump, creating directional graphs, transforming them into directional trees, and executing the heuristic search engine.


        * PerfAnal is a GUI-based tool for analyzing the performance of applications on the Java 2 Platform. You can use PerfAnal to identify performance problems in your code and locate code that needs tuning.


        * JAMon is a free, simple, high performance, thread safe, Java API that allows developers to easily monitor production applications.


        * Eclipse Memory Analyzer is a fast and feature-rich Java heap analyzer that helps you find memory leaks and reduce memory consumption.


        * GCViewer is a free open source tool to visualize data produced by the Java VM options -verbose:gc and -Xloggc:<file>. It also calculates garbage collection related performance metrics (throughput, accumulated pauses, longest pause, etc.).

     

    If you are running your application on HP-UX platform, check out:


        * HPjmeter

              o Identify and diagnose performance problems in Java?? applications running on HP-UX
              o Monitor live Java?? applications and analyze profiling data
              o Capture profiling data with zero preparation when using JDK/JRE 5.0.04 or higher
              o Run the HPjmeter console on HP-UX, Linux, and Windows?? systems
              o Improve garbage collection performance


        * HPjconfig is a Java configuration tool for tuning your HP-UX 11i HP Integrity Itanium?? and HP 9000 PA-RISC system kernel parameters to match the characteristics of your application. HPjconfig provides kernel parameter recommendations tailored to your HP-UX hardware platform. It offers save and restore functions for easy distribution of tailored recommendations across your customer base. When given specific Java and HP-UX versions, HPjconfig will determine if all of the latest HP-UX patches required for Java performance and functionality are installed on the system, and highlight any missing or superseded patches.


        * Java Out-of-Box Tool is a stand-alone bundle that upon installation will install startup (RC) scripts, modify kernel parameters, rebuild the kernel, and reboot the system. During startup, the startup scripts will modify system tunables, thus providing better “Out of The Box” behavior. for Java.

     

  • Java 开发 2.0: 用 Hadoop MapReduce 进行大数据分析(转)

    2011-05-08 00:16:22

    Java 开发 2.0: 用 Hadoop MapReduce 进行大数据分析


    Google 在 2001 年发布图像搜索功能时,只有 2.5 亿索引图像,不到 10 年,这个巨大的搜索功能已经可以检索超过 100 亿个图像了,每分钟有 35 小时的内容上传到 YouTube。据称,Twitter 每天平均处理 5500 万 tweet。今年早些时候,搜索功能每天记录 6 亿条查询记录。这 就是我们讨论大数据的意义所在。
    关于本系列

    从 Java 技术首次亮相以来,Java 开发的格局已经发生了巨大的变化。得益于成熟的开源框架和可靠的租用式部署基础设施,现在已经可以迅速经济地汇编、测试、运行和维护 Java 应用程序了。在 本系列 中,Andrew Glover 将探索使这种全新开发范例成为可能的各种技术和工具。

    如此大规模的数据一度仅限于大企业、学校和政府机构 — 这些机构有能力购买昂贵的超级计算机、能够雇用员工保障其运行。今天,由于存储成本的降低和处理能力的商品化,一些小公司,甚至个人都可以存储和挖掘同样的数据,推动新一轮的应用程序创新。

    大数据革命技术之一是 MapReduce,一个编程模式,是 Google 针对大规模、分布式数据而开发的。在本文中,我将介绍 Apache 的开源 MapReduce 实现,Hadoop,也有人将其称之为云计算的杀手应用程序。

    关于 Hadoop

    Apache 的 Hadoop 框架本质上是一个用于分析大数据集的机制,不一定位于数据存储中。Hadoop 提取出了 MapReduce 的大规模数据分析引擎,更易于开发人员理解。Hadoop 可以扩展到无数个节点,可以处理所有活动和相关数据存储的协调。

    Hadoop 的众多特性和配置使其成为一个十分有用且功能强大的框架,其用途和功能令人惊讶。Yahoo! 以及其他许多组织已经找到了一个高效机制来分析成堆的字节数。在单个节点上运行 Hadoop 也很容易;您所需要的只是一些需要分析的数据,以及熟悉一般的 Java 代码。Hadoop 也可和 Ruby、Python 以及 C++ 一起使用。
    了解更多 MapReduce

    如果您是本系列的读者,您可能已经见过 MapReduce 一两次了。在 “通过 CouchDB 和 Groovy 的 RESTClient 实现 REST” 中,我介绍了 CouchDB 如何利用 MapReduce 进行查看,接着在 “MongoDB:拥有 RDBMS 特性的 NoSQL 数据存储” 中我再次提到 MapReduce,处理 MongoDB 文档的机制。

    作为处理大数据集的概念框架,MapReduce 对于使用许多计算机来解决分布式问题而言是高度优化的。顾名思义,这个框架由两个函数构成。map 函数专用于获取大数据输入,并将其分成小片段,然后交由其他进程进行操作。reduce 函数整理 map 收集的各个回应,然后显示最后的输出。

    在 Hadoop 中,您可以通过扩展 Hadoop 自身的基类来定义 map 和 reduce 实现。实现和输入输出格式被一个指定它们的配置联系在一起。Hadoop 非常适合处理包含结构数据的大型文件。Hadoop 可以对输入文件进行原始解析,这一点特别有用,这样您就可以每次处理一行。定义一个 map 函数实际上只是一个关于确定您从即将输入的文本行中捕获什么内容的问题。

    回页首

    数据,无处不在的数据!

    美国政府产生大量数据,只有一部分是普通民众所感兴趣的。各种政府机构免费发布关于 US 经济健康状况和更改社会人口统计资料的数据。U.S. Geological Survey (USGS) 发布国内外地震数据。

    世界各地每天都有很多个小型地震发生。其中大多数发生在地壳深处,没有人能感觉到,尽管如此,但是监听站仍然会进行记录。USGS 以 CSV(或逗号分隔值)文件的格式发布每周地震数据。

    每周文件平均不是很大 — 只有大约 100 KB 左右。但是,它可以作为学习 Hadoop 的基础。记住,Hadoop 有能力处理更 大的数据集。

    跟踪震动

    我近期从 USGS 网站下载的 CSV 文件有大约 920 多行。如 清单 1 所示:


    清单 1. 清单 1. 一个 USGS 地震数据文件的行数统计

                   
    $> wc -l eqs7day-M1.txt
      920 eqs7day-M1.txt


    CVS 文件内容如 清单 2 所示(这是前两行):


    清单 2. 清单 2. CVS 文件的前两行

                   
    $> head -n 2 eqs7day-M1.txt
    Src,Eqid,Version,Datetime,Lat,Lon,Magnitude,Depth,NST,Region
    ci,14896484,2,"Sunday, December 12, 2010 23:23:20 UTC",33.3040,-116.4130,1.0,11.70,22,
      "Southern California"


    这就是我称之为信息丰富 的文件,尤其是当您想到它总共有 920 行记录时。然而我只想知道在该文件报告的这一周内每一天有多少次地震发生。我想知道在这 7 天内哪个区域是地震频发区。

    我第一个想到的就是使用简单的 grep 命令来搜索每天的地震数。看看这个文件,我发现数据记录是从 12 月 12 开始的。因此我对该字符串执行了一次 grep -c,其结果如清单 3 所示:


    清单 3. 清单 3. 12 月 12 有多少次地震发生?

                   
    $> grep -c 'December 12' eqs7day-M1.txt
    98


    安装 Hadoop

    如果您之前没有安装 Hadoop,那么现在就装。第一步,下载最新版二进制文件,解压,然后在您的路径上设置 Hadoop 的 bin 目录。完成这些您就可以直接执行 hadoop 命令了。使用 Hadoop 要求您执行它的 hadoop 命令,而不是像您所见到的那样调用 java 命令。您可以向 hadoop 命令传选项,诸如在哪里可以找到您的 Java 二进制文件(例如,表示您的 map 和 reduce 实现)。在我的示例中,我创建了一个 jar 文件,告诉 Hadoop 我想在我的 jar 文件内运行哪个任务。我也向 Hadoop 类路径添加了一些运行我的应用程序所需的附加二进制文件。

    现在,我知道在 12 月 12 日有 98 条记录,也就是说有 98 次地震。我只能沿着这条记录向下,对 12 月 10 日的记录执行一次 grep,接着是 11 号,等等。这听起来有点乏味。更糟糕的是,我还需要知道在该文件中的是哪几天。我确实不关心这些,甚至有时候我可能无法获取该信息。事实上,我只想知道在七天这样一个时间段内任何一天的地震次数,使用 Hadoop 我就可以很容易的获取这一信息。

    Hadoop 只需要几条信息就可以回答我的第一个和第二个问题:即,要处理哪条输入以及如何处理 map 和 reduce。我也必须提供了一个可以将每件事都联系起来的作业。在我开始处理这些代码之前,我需要花点时间确定我的 CSV 数据整齐有序。

    回页首

    使用 opencsv 进行数据解析

    除了地震 CSV 文件的第一行之外,第一行是文件头,每一行都是一系列逗号分隔数据值。我只对数据的 3 个部分感兴趣:日期、地点和震级。为了获取这些资料,我将使用一个很棒的开源库 opencsv,它将会帮助我分析 CSV 文件。

    作为一个测试优先的工具,我首先编写一个快捷 JUnit 测试,确认我可以从 CSV 文件的一个样例行获取的我所需要的信息,如清单 4 所示:


    清单 4. 清单 4. 解析一个 CSV 行

                   
    public class CSVProcessingTest {

     private final String LINE = "ci,14897012,2,\"Monday, December 13, 2010 " +
                "14:10:32 UTC\",33.0290,-115." +
                "5388,1.9,15.70,41,\"Southern California\"";

     @Test
     public void testReadingOneLine() throws Exception {
      String[] lines = new CSVParser().parseLine(LINE);

      assertEquals("should be Monday, December 13, 2010 14:10:32 UTC",
        "Monday, December 13, 2010 14:10:32 UTC", lines[3]);

      assertEquals("should be Southern California",
        "Southern California", lines[9]);

      assertEquals("should be 1.9", "1.9", lines[6]);
     }
    }


    正如您在 清单 4 中所看到的,opencsv 处理逗号分隔值非常容易。该解析器仅返回一组 String,所以有可能获取位置信息(别忘了,在 Java 语言中数组和集合的访问是从零开始的)。

    转换日期格式

    当使用 MapReduce 进行处理时,map 函数的任务是选择一些要处理的值,以及一些键。这就是说,map 主要处理和返回两个元素:一个键和一个值。回到我之前的需求,我首先想知道每天会发生多少次地震。因此,当我在分析地震文件时,我将发布两个值:键是日期,值是一个计数器。reduce 函数将对计数器(只是一些值为 1 的整数)进行总计。因此,提供给我的是在目标地震文件中某一个日期出现的次数。

    由于我只对 24 小时时段内的信息感兴趣,我得剔除每个文件中的日期的时间部分。在 清单 5 中,我编写了一个快速测试,验证如何将一个传入文件中的特定日期信息转换成一个更一般的 24 小时日期:


    清单 5. 清单 5. 日期格式转换

                   
    @Test
    public void testParsingDate() throws Exception {
     String datest = "Monday, December 13, 2010 14:10:32 UTC";
     SimpleDateFormat formatter = new SimpleDateFormat("EEEEE, MMMMM dd, yyyy HH:mm:ss Z");
     Date dt = formatter.parse(datest);

     formatter.applyPattern("dd-MM-yyyy");
     String dtstr = formatter.format(dt);
     assertEquals("should be 13-12-2010", "13-12-2010", dtstr);
    }


    在 清单 5 中,我使用了 SimpleDateFormat Java 对象,将 CSV 文件中格式为 Monday, December 13, 2010 14:10:32 UTC 的日期 String 转换成了更一般的 13-12-2010。

    回页首

    Hadoop 的 map 和 reduce

    现在我已经找到了处理 CSV 文件以及其日期格式的解决方法。我要开始在 Hadoop 中实施我的 map 和 reduce 函数了。这个过程需要理解 Java 泛型,因为 Hadoop 选择使用显式类型,为了安全起见。

    当我使用 Hadoop 定义一个映射实现时,我只扩展 Hadoop 的 Mapper 类。然后我可以使用泛型来为传出键和值指定显式类。类型子句也指定了传入键和值,这对于读取文件分别是字节数和文本行数。

    EarthQuakesPerDateMapper 类扩展了 Hadoop 的 Mapper 对象。它显式地将其输出键指定为一个 Text 对象,将其值指定为一个 IntWritable,这是一个 Hadoop 特定类,实质上是一个整数。还要注意,class 子句的前两个类型是 LongWritable 和 Text,分别是字节数和文本行数。

    由于类定义中的类型子句,我将传入 map 方法的参数类型设置为在 context.write 子句内带有该方法的输出。如果我想指定其他内容,将会出现一个编译器问题,或 Hadoop 将输出一个错误消息,描述类型不匹配的消息。


    清单 6. 清单 6. 一个映射实现

                   
    public class EarthQuakesPerDateMapper extends Mapper<LongWritable,
      Text, Text, IntWritable> {
     @Override
     protected void map(LongWritable key, Text value, Context context) throws IOException,
       InterruptedException {

      if (key.get() > 0) {
       try {
         CSVParser parser = new CSVParser();
         String[] lines = parser.parseLine(value.toString());

         SimpleDateFormat formatter =
           new SimpleDateFormat("EEEEE, MMMMM dd, yyyy HH:mm:ss Z");
         Date dt = formatter.parse(lines[3]);
         formatter.applyPattern("dd-MM-yyyy");

         String dtstr = formatter.format(dt);
         context.write(new Text(dtstr), new IntWritable(1));
       } catch (ParseException e) {}
      }
     }
    }


    清单 6 中的 map 实现比较简单:本质上是,Hadoop 为在输入文件中找到的每一行文本调用这个类。为了避免除了 CSV 头部,首先检查是否字节数(key 对象)为零。然后执行清单 4 和 5 中的步骤:捕获传入日期,进行转换,然后设置为传出键。我也提供了一个数:1。就是说,我为每个日期编写一个计数器,当 reduce 实现被调用时,获取一个键和一系列值。在本例中,键是日期及其值,如 清单 7 所示:


    清单 7. 清单 7. 一个 map 输出和 reduce 输入的逻辑视图

                   
    "13-12-2010":[1,1,1,1,1,1,1,1]
    "14-12-2010":[1,1,1,1,1,1]
    "15-12-2010":[1,1,1,1,1,1,1,1,1]


    注意,context.write(new Text(dtstr), new IntWritable(1))(在 清单 6 中)构建了如 清单 7 所示的逻辑集合。正如您所了解的,context 是一个保存各种信息的 Hadoop 数据结构。context 被传递到 reduce 实现,reduce 获取这些值为 1 的值然后总和起来。因此,一个 reduce 实现逻辑上创建如 清单 8 所示的数据结构:


    清单 8. 清单 8. 一个 reduce 输出视图

                   
    "13-12-2010":8
    "14-12-2010":6
    "15-12-2010":9


    我的 reduce 实现如 清单 9 所示。与 Hadoop 的 Mapper 一样,Reducer 被参数化了:前两个参数是传入的键类型(Text)和值类型(IntWritable),后两个参数是输出类型:键和值,这在本例中是相同的。


    清单 9. 清单 9. reduce 实现

                   
    public class EarthQuakesPerDateReducer extends Reducer<Text, IntWritable, Text,
      IntWritable> {
     @Override
     protected void reduce(Text key, Iterable<IntWritable> values, Context context)
      throws IOException, InterruptedException {
      int count = 0;
      for (IntWritable value : values) {
       count++;
      }
      context.write(key, new IntWritable(count));
     }
    }


    我的 reduce 实现非常简单。正如我在 清单 7 中所指出的,传入的是实际上是一个值的集合,在本例中是 1 的集合,我所做的就是将它们加起来,然后写出一个新键值对表示日期和次数。我的 reduce 代码可以挑出您在 清单 8 中所见到的这几行。逻辑流程看起来像这样:

    "13-12-2010":[1,1,1,1,1,1,1,1] -> "13-12-2010":8


    当然,这个清单的抽象形式是 map -> reduce。

    回页首

    定义一个 Hadoop Job

    现在我已经对我的 map 和 reduce 实现进行了编码,接下来所要做的是将所有这一切链接到一个 Hadoop Job。定义一个 Job 比较简单:您需要提供输入和输出、map 和 reduce 实现(如 清单 6 和 清单 9 所示)以及输出类型。在本例中我的输出类型和 reduce 实现所用的是同一个类型。


    清单 10. 清单 10. 一个将 map 和 redece 绑在一起的 Job

                   
    public class EarthQuakesPerDayJob {

     public static void main(String[] args) throws Throwable {

      Job job = new Job();
      job.setJarByClass(EarthQuakesPerDayJob.class);
      FileInputFormat.addInputPath(job, new Path(args[0]));
      FileOutputFormat.setOutputPath(job, new Path(args[1]));

      job.setMapperClass(EarthQuakesPerDateMapper.class);
      job.setReducerClass(EarthQuakesPerDateReducer.class);
      job.setOutputKeyClass(Text.class);
      job.setOutputValueClass(IntWritable.class);

      System.exit(job.waitForCompletion(true) ? 0 : 1);
     }
    }


    在 清单 10 中,我使用一个 main 方法将所有这一切绑在一起,该方法有两个参数:地震 CSV 文件的目录,以及生成报告的输出目录(Hadoop 更喜欢创建该目录)。

    为了执行这个小框架,我需要将这些类打包。我还需要告知 Hadoop 在哪里可以找到 opencsv 二进制文件。然后可以通过命令行执行 Hadoop ,如 清单 11 所示:


    清单 11. 清单 11. 执行 Hadoop

                   
    $> export HADOOP_CLASSPATH=lib/opencsv-2.2.jar
    $> hadoop jar target/quake.jar com.b50.hadoop.quake.EarthQuakesPerDayJob
       ~/temp/mreduce/in/ ~/temp/mreduce/out


    运行这些代码,Hadoop 开始运行时您将可以看到一堆文本在屏幕上一闪而过。我所用的 CSV 文件相比专门用于处理这种情况的 Hadoop,那真是小巫见大巫!hadoop 应该可以在几秒钟内完成,具体取决于您的处理功能。

    完成这些后,您可以使用任何编辑器查看输出文件内容。还可以选择直接使用 hadoop 命令。正如 清单 12 所示:


    清单 12. 清单 12. 读取 Hadoop 输出

                   
    $> hadoop dfs -cat part-r-00000
    05-12-2010      43
    06-12-2010      143
    07-12-2010      112
    08-12-2010      136
    09-12-2010      178
    10-12-2010      114
    11-12-2010      114
    12-12-2010      79


    如果您像我一样,在 清单 12 中首先会注意到的就是每天地震数 — 12 月 9 日就有 178 次地震。希望您也会注意到 Hadoop 实现了我所想要的:整齐地列出我的研究范围内每天的地震次数。

    回页首

    编写另一个 Mapper

    接下来,我想找到地震发生在哪里,以及如何快速计算出在我的研究范围内记录地震次数最多的是哪个区域。当然,您已经猜到了,Hadoop 可以轻松地做到。在这个案例中,键不再是日期而是区域。因此,我编写了一个新的 Mapper 类。


    清单 13. 清单 13. 一个新的 map 实现

                   
    public class EarthQuakeLocationMapper extends Mapper<LongWritable, Text, Text,
      IntWritable> {
     @Override
     protected void map(LongWritable key, Text value, Context context) throws IOException,
      InterruptedException {
      if (key.get() > 0) {
       String[] lines = new CSVParser().parseLine(value.toString());
       context.write(new Text(lines[9]), new IntWritable(1));
      }
     }
    }


    和之前获取日期然后进行转换相比,在 清单 13 中我所作的是获取位置,这是 CSV 阵列中的最后一个条目。

    相比一个庞大的位置和数字列表,我将结果限制在那些 7 天内出现 10 次的区域。


    清单 14. 清单 14. 哪里的地震较多?

                   
     public class EarthQuakeLocationReducer extends
    Reducer<Text, IntWritable, Text, IntWritable> { @Override protected void
    reduce(Text key, Iterable<IntWritable> values, Context context) throws
    IOException, InterruptedException { int count = 0; for (IntWritable value :
    values) { count++; } if (count >= 10) { context.write(key, new
    IntWritable(count)); } } }


    清单 14 中的代码和 清单 9 中的代码非常类似;然而,在本例中,我限制了输出大于或等于 10。接下来,我将 map 和 reduce,以及其他 Job 实现绑在一起,进行打包,然后和平常一样执行 Hadoop 获取我的新答案。

    使用 hadoop dfs 目录显示我所请求的新值:


    清单 15. 清单 15. 地震区域分布

                   
    $> hadoop dfs -cat part-r-00000
    Andreanof Islands, Aleutian Islands, Alaska     24
    Arkansas        40
    Baja California, Mexico 101
    Central Alaska  74
    Central California      68
    Greater Los Angeles area, California    16
    Island of Hawaii, Hawaii        16
    Kenai Peninsula, Alaska 11
    Nevada  15
    Northern California     114
    San Francisco Bay area, California      21
    Southern Alaska 97
    Southern California     115
    Utah    19
    western Montana 11



    从 清单 15 还可以得到什么?首先,北美洲西海岸,从墨西哥到阿拉斯加是地震高发区。其次,阿肯色州明显位于断带层上,这是我没有意识到的。最后,如果您居住在北部或者是南加州(很多软件开发人员都居住于此),您周围的地方每隔 13 分钟会震动一次。

    回页首

    结束语

    使用 Hadoop 分析数据轻松且高效,对于它对数据分析所提供的支持,我只是了解皮毛而已。Hadoop 的设计旨在以一种分布式方式运行,处理运行 map 和 reduce 的各个节点之间的协调性。作为示例,本文中我只在一个 JVM 上运行 Hadoop,该 JVM 仅有一个无足轻重的文件。

    Hadoop 本身是一个功能强大的工具,围绕它还有一个完整的、不断扩展的生态系统,可以提供子项目至基于云计算的 Hadoop 服务。Hadoop 生态系统演示了项目背后丰富的社区活动。来自社区的许多工具证实了大数据分析作为一个全球业务活动的可行性。有了 Hadoop,分布式数据挖掘和分析对所有软件创新者和企业家都是可用的,包括但不限于 Google 和 Yahoo! 这类大企业。


    参考资料

    学习

        * Java 开发 2.0:这个 dW 系列讨论重定义 Java 开发格局的技术;近期话题包括 MongoDB( 2010 年 9 月);CouchDB(2009 年 11 月)和 Objectify AppEngine(2010 年 11 月)。

        * “用 Hadoop 进行分布式数据处理,第 1 部分:入门” (M. Tim Jones,developerWorks,2010 年 5 月):这篇文章 — 系列的第一篇 — 介绍了 Hadoop 框架,包括 Hadoop 文件系统(HDFS)和常用的节点类型。介绍了如何按装和配置一个单节点 Hadoop 集群并深入研究了 MapReduce 应用程序。最后,发现了使用其核心 Web 接口监控和管理 Hadoop 的方法。 另外请参阅 第 2 部分和 第 3 部分。

        * “在云中使用 MapReduce 和负载平衡”(Kirpal A. Venkatesh,et. al.,developerWorks,2010 年 7 月):了解 Hadoop MapReduce 和虚拟化如何改进节点性能。

        * “A profile of Apache Hadoop MapReduce computing efficiency, Part 1” (Paul Burkhardt,Cloudera Development Center,2010 年 12 月):一个关于 MapReduce 应用程序如何高效地使用计算资源的两部分系列文章,第一部分是对计算效率的一个概述,因为这涉及到评估 Hadoop MapReduce 应用程序。

        * “Hadoop companies everywhere”(Alex Handy,SD Times,2009 年 7 月):公司每天都产生很多数据,但是很多都不能从其中获取业务智能。这创造了机会。

        * 浏览 Java 技术书店 阅读关于这些和其他技术主题的图书。

        * developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。

    获得产品和技术

        * 下载 Hadoop MapReduce:一个 Apache Software Foundation 项目。

        * Get opencsv:从 SourceForge 下载它。

    讨论

        * 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

    关于作者
    Andrew Glover

    Andrew Glover 是具有行为驱动开发、持续集成和敏捷软件开发激情的开发人员、作家、演说家和企业家。他是 easyb 行为驱动开发(Behavior-Driven Development,BDD)框架的创建者和三本书的合著者:持续集成、Groovy 在行动 和 Java 测试模式。您可以通过他的博客与他保持一致并在 Twitter(http://twitter.com/aglover)上关注他。

    建议
    //

    0 条评论 | 登录添加评论举报不良信息
    添加评论

    标有星号(*)的是必填项目。

    评论:*

     

    快来添加第一条评论

    显示最新的 5 条评论 | 显示后 5 条评论 | 显示所有评论

    登录添加评论

    回页首
    static.content.url=http://www.ibm.com/developerworks/js/artrating/
    SITE_ID=10
    Zone=Java technology, Open source, Cloud computing
    ArticleID=648617
    ArticleTitle=Java 开发 2.0: 用 Hadoop MapReduce 进行大数据分析
    publish-date=04182011
    author1-email=ajglover@gmail.com
    author1-email-cc=
    document.write('
    url='+location.href.replace(/
    ');
    url=http://www.ibm.com/developerworks/cn/java/j-javadev2-15/index.html
    内容

        * 关于 Hadoop
        * 数据,无处不在的数据!
        * 使用 opencsv 进行数据解析
        * Hadoop 的 map 和 reduce
        * 定义一个 Hadoop Job
        * 编写另一个 Mapper
        * 结束语
        * 参考资料
        * 关于作者
        * 建议

    标签
    Help
    使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

    使用 滑动条 调节标签的数量。

    热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

    我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。
    使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

    热门文章标签 |
    我的文章标签跳转到标签列表

    热门文章标签 |
    我的文章标签
    跳转到标签列表
    热门标签

        * _unassigned (26) 
        * 1 (8) 
        * 2 (9) 
        * 5 (3) 
        * ajax (16) 
        * ant (14) 
        * apache (24) 
        * apache_axis (3) 
        * api (32) 
        * best_practic... (131) 
        * cloud-computin... (4) 
        * concurrency (3) 
        * developer_kits (3) 
        * eclipse (10) 
        * ejb_(enterpris... (5) 
        * generic (11) 
        * hadoop (4) 
        * html (5) 
        * i/o (4) 
        * ibatis (7) 
        * j2ee (5) 
        * j2ee_(java_2_... (12) 
        * j2se_(java_2_... (36) 
        * java (127) 
        * java_技术 (84) 
        * java_入门 (8) 
        * java-spring (8) 
        * javascript. (8) 
        * jmx (5) 
        * jpa (9) 
        * jsf (8) 
        * jsf_(javaserve... (7) 
        * jsp_(javaserve... (7) 
        * jvm_(java_vir... (9) 
        * maven (4) 
        * rest (4) 
        * scala (5) 
        * selenium (5) 
        * servlet (40) 
        * spring (43) 
        * struts (7) 
        * test (4) 
        * tomcat (26) 
        * uml (5) 
        * web (14) 
        * web_服务 (13) 
        * web_应用 (7) 
        * xml (13) 
        * 安全 (14) 
        * 编码 (8) 
        * 标准 (6) 
        * 部分 (7) 
        * 部分: (7) 
        * 持久性 (9) 
        * 第 (4) 
        * 调试 (9) 
        * 多线程 (15) 
        * 工作原理解析 (5) 
        * 管理 (4) 
        * 架构模式 (4) 
        * 开放源码 (72) 
        * 模式 (9) 
        * 配置 (15) 
        * 设计模式 (18) 
        * 实现 (4) 
        * 事务 (4) 
        * 数据访问 (7) 
        * 数据库和数据管理 (18) 
        * 算法 (11) 
        * 体系架构 (5) 
        * 网络 (4) 
        * 无线技术 (11) 
  • 第一章:Ruby语言最小化

    2011-04-30 01:03:40

     

    谈到Ruby语言,这里只是简单解释了理解第一部分所需的知识。这里并没有完全指出编程中需要注意的地方,本章的目的在于阅读,而非编写Ruby程序。有Ruby经验的人可以放心的跳过这一章。

    关于语法,在第二部分中,我们会一直谈到令人厌倦,所以,本章就不做详尽的讨论了。只有那些用得最多的,比如hash字面量,会谈到一些变化。原则上,不省略那些可能省略的东西。因为这样会让语法规则变得简单,不必到处去写“此处省略”。

    对象 Top

    字符串

    Ruby程序中可操作的东西都是对象,没有像Java的int和long这样的基本类型(primitive)。比如,下面这样写就是一个内容为“content”的字符串对象(String对象)。

    Ruby代码 复制代码 收藏代码
    1. "content"   


    简单的说,这里调用了一个字符串对象,更准确的说法是,这是一个产生字符串对象的“表达式”。因此,如果你写了多次,那么每次都会生成一个字符串对象。

    Ruby代码 复制代码 收藏代码
    1. "content"    
    2. "content"    
    3. "content"   


    这里生成了三个内容同为“content”的对象。

    对了,仅仅这样,程序员是无法看到对象的。下面是在终端上显示对象的方法。

    Ruby代码 复制代码 收藏代码
    1. p("content")   # 显示"content"   


    “#”之后是注释。今后,结果都会以注释的形式给出。

    “p(……)”表示调用函数p,任何对象都可以显示出来,基本上,它就是一个调试函数。

    严格说来,Ruby没有函数,不过,可以把它认为是函数。这个“函数”可以用在任何地方。

    各种各样的字面量(literal)

    接下来,稍微说明一下可以直接生成对象的表达式(字面量)。先来看一下整数和小数。

    Ruby代码 复制代码 收藏代码
    1. # 整数   
    2. 1   
    3. 2   
    4. 100   
    5. 9999999999999999999999999   # 多大的数都能用   
    6.   
    7. # 小数   
    8. 1.0   
    9. 99.999   
    10. 1.3e4     # 1.3×10^4  


    不要忘了,这些都是生成对象的表达式。重复一下,Ruby中没有“基本类型”。

    下面的表达式生成数组对象。

    Ruby代码 复制代码 收藏代码
    1. [1, 2, 3]  


    这段程序生成了一个数组,它按顺序包含1、2、3三个整数作为其元素。数组元素可以使用任意对象,这样也可以。

    Ruby代码 复制代码 收藏代码
    1. [1, "string", 2, ["nested""array"]]  


    下面的表达式生成hash表。

    Ruby代码 复制代码 收藏代码
    1. {"key"=>"value""key2"=>"value2""key3"=>"value3"}  


    hash表是一种在任意对象间建立一一对应关系的结构。上面表达的是一个如下关系的表。

    Ruby代码 复制代码 收藏代码
    1. "key"   →  "value"    
    2. "key2"  →  "value2"    
    3. "key3"  →  "value3"   


    对这样创建出的一个hash表对象,这样问,“什么与‘key’对应?”,如果它能够听到的话,“是‘value’。” 怎么听?使用方法。

    方法调用

    方法可以通过对象调用。C++术语称之为成员函数。没有必要深入解释什么是方法, 这里只简单解释一下记法。

    Ruby代码 复制代码 收藏代码
    1. "content".upcase()  


    这表示对字符串对象(其内容为“content”)调用upcase方法。upcase是这样一个方法,它返回一个新的字符串对象,将小写字母转换为大写字母,其结果如下:

    Ruby代码 复制代码 收藏代码
    1. p("content".upcase())   # 显示"CONTENT"   


    方法可以连续调用。

    Ruby代码 复制代码 收藏代码
    1. "content".upcase().downcase()  


    这里对"content".upcase()的返回值对象调用downcase方法。

    另外,不同于Java或C++拥有公有的字段(成员变量),Ruby对象的接口只有方法。

    程序 Top

    顶层(top level)

    直接写出Ruby表达式就是程序,不必像Java或C++那样定义main()。

    Ruby代码 复制代码 收藏代码
    1. p("content")  


    这就是一个完整的程序了。把这段代码放到一个名为first.rb的文件中,就可以在命令行下执行了:

    Ruby代码 复制代码 收藏代码
    1. % ruby first.rb   
    2. "content"   


    如果使用ruby的-e选项,连创建文件都省了。

    Ruby代码 复制代码 收藏代码
    1. % ruby -e 'p("content")'  
    2. "content"   


    对了,p所写的地方在程序的嵌套层次上是最低的,也就是说,它位于程序的最上层,称为“顶层”。拥有顶层是Ruby作为脚本语言的一大特征。

    在Ruby中,基本上一行就是一条语句,最后面无需分号。因此,下面的程序可以解释为三条语句。

    Ruby代码 复制代码 收藏代码
    1. p("content")   
    2. p("content".upcase())   
    3. p("CONTENT".downcase())  


    如果执行的话,结果是这样。

    Ruby代码 复制代码 收藏代码
    1. % ruby second.rb   
    2. "content"    
    3. "CONTENT"    
    4. "content"   


    局部变量

    在Ruby中,无论是变量还是常量,全都保持着引用(reference)。因此,将一个变量赋值给另一个变量时,不会发生复制。可以考虑一下Java的对象类型变量、C++的对象指针。然而,指针自身的值是无法修改的。

    Ruby变量的首字符表示变量的类型(作用域)。以小写字母或下划线开头的表示局部变量。 “=”表示赋值。

    Ruby代码 复制代码 收藏代码
    1. str = "content"    
    2. arr = [1,2,3]  


    最初的赋值兼做变量声明,无需额外声明。变量是无类型的,因此,无论何种类型,都可以无差别的赋值。下面的程序完全合法。

    Ruby代码 复制代码 收藏代码
    1. lvar = "content"    
    2. lvar = [1,2,3]   
    3. lvar = 1  


    虽说可以,不过没有必要。把不同类型的变量赋值给一个变量通常只能增加阅读的难度。实际的Ruby程序中很少这样做。这里的做法完全是为了举例。

    访问变量是一种常见的记法。

    Ruby代码 复制代码 收藏代码
    1. str = "content"    
    2. p(str)           # 显示"content"   


    随后是一个变量保持引用的例子。

    Ruby代码 复制代码 收藏代码
    1. a = "content"    
    2. b = a   
    3. c = b  


    程序执行后,a、b、c三个局部变量指向同一个对象——第一行生成的字符串对象“content”,如图1所示。


    图1: Ruby变量保持对象引用


    对了,这里的“局部”是某种程度上的局部,暂且还无法解释它的范围。先要说一下,顶层只是一种“局部”作用域。

    常量

    变量名以大写字母开头的是常量。因为是常量,只能对它进行一次(第一次)赋值。

    Ruby代码 复制代码 收藏代码
    1. Const = "content"    
    2. PI = 3.1415926535   
    3.   
    4. p(Const)   # 显示"content"   


    再次赋值会造成错误。说实话,只是警告,不是错误。这么做是为了让一些操作Ruby程序本身的应用程序,比如开发环境,在加载两次文件的时候,不会报错。也就是说,这是为实用而做的妥协,本来应该报错。实际上,直到1.1版本都会报错。

    Ruby代码 复制代码 收藏代码
    1. C = 1   
    2. C = 2   # 实际中只是给出警告,理论上应该报错  


    很多人为“常量”这个词所欺骗,认为常量就是“所指对象一旦被记住,便不再改变”。实际上,并不是常量所指的对象不再改变。如果用英语表达的话,read only比constant更能表现其意图(图2)。顺便说一下,另有一个名为freeze的方法用于让对象本身不变。


    图2: 常量read only的含义


    实际上,常量的作用域还没有谈到。在下一节中,我们会结合类来谈一下。

    控制结构

    Ruby的控制结构很丰富,单单罗列出来就很困难了。先来谈谈if和while。

    Ruby代码 复制代码 收藏代码
    1. if i < 10 then  
    2.   # 主体   
    3. end  
    4.   
    5. while i < 10 do  
    6.   # 主体   
    7. end  


    对于条件表达式,只有两个对象——false和nil——为假,其余所有对象都是真。0和空字符串也是真。

    顺便说一下,当然不会只有false,还有true。它当然是真。

    类与方法 Top



    本来在面向对象系统中,方法属于对象。但那完全是理想世界的事。在普通的程序中,会有大量拥有相同方法集合的程序,坦率的说,以对象为单位去记忆其所拥有的方法并不是件容易的事。通常是用类或多方法(multi method)这样的结构来减少方法的重复定义。

    在Ruby中,将对象与方法连在一起的机制采用了传统的”类”的概念。也就是说,所有对象都属于某个类,由类决定可以调用的方法。这时,就称对象是“某某类的实例(instance)”。

    比如,字符串“str”是String类的一个实例。并且,String类定义了upcase、downcase、strip,以及其它一些方法,所有字符串对象都可以响应这些方法。

    Ruby代码 复制代码 收藏代码
    1. # 因为全都属于字符串类,所以定义了同样的方法   
    2.        "content".upcase()   
    3. "This is a pen.".upcase()   
    4.     "chapter II".upcase()   
    5.   
    6.        "content".length()   
    7. "This is a pen.".length()   
    8.     "chapter II".length()  


    不过,如果调用的方法没有定义怎么办?静态语言的话,会造成编译错误,而Ruby则会成为运行时异常。实际试一下。这种长度的程序还是用-e运行比较方便。

    Ruby代码 复制代码 收藏代码
    1. % ruby -e '"str".bad_method()'  
    2. -e:1: undefined method `bad_method' for "str":String (NoMethodError)  


    如果找不到方法,就会抛出一个名为NoMethodError的错误。

    最后,为那个一遍遍说的令人厌烦的“String的upcase方法”准备了一个专用的记法。 “String#upcase”表示“定义在String类中的upcase方法”。

    顺便说一下,写成“String.upcase”在Ruby世界里有完全不同的意思。至于是什么意思?下节分解。

    类的定义

    迄今为止,都是已经定义的类。当然,还可以定义自己的类。类可以用class语句定义。

    Ruby代码 复制代码 收藏代码
    1. class C   
    2. end  


    这里定义了一个新的类C。定义后,可以如下这样用。

    Ruby代码 复制代码 收藏代码
    1. class C   
    2. end  
    3. c = C.new()   # 生成C的实例,赋值给变量c  


    请注意,生成实例用的不是new C。敏锐的读者可能注意到了,C.new()的记法像是一个方法调用。在Ruby中,生成对象的表达式只是一个方法调用。

    首先,Ruby的类名与常量名是等价的。那么,与类名同名的常量是什么呢?实际上,就是这个类。在Ruby中,所有能操作的东西都是对象。类自然也是对象。这种对象称为类对象。所有的类对象都是Class类的实例。

    也就是说,创建新类对象的class语句,其动作是将类对象赋值给一个与类同名的常量。另一方面,生成实例的操作是,访问这个常量,通过该对象调用方法(通常是new)。看看下面的例子就可以知道,实例生成同普通的方法调用没有区别。

    Ruby代码 复制代码 收藏代码
    1. S = "content"    
    2. class C   
    3. end  
    4.   
    5. S.upcase()  # 得到常量S表示的对象,调用upcase方法   
    6. C.new()     # 得到常量C表示的对象,调用new方法  


    正是因为这样,Ruby中没有new这个保留字。

    接下来,可以用p将生成的类实例显示出来。

    Ruby代码 复制代码 收藏代码
    1. class C   
    2. end  
    3.   
    4. c = C.new()   
    5. p(c)       # #<C:0x2acbd7e4>  


    不过,它到底无法像字符串和整数那样表示得那么漂亮,显示的是类名和所属的内部ID。这个ID表示的是指向该对象指针的值。

    是的是的,可能你已经完全忘了方法名的记法。 “Object.new”表示通过类对象Object本身调用new方法。因此,“Object#new”和“Object.new”完全是两码事,需要严格区分。

    obj = Object.new()   # Object.new
    obj.new()            # Object#new

    实际上,Object#new并没有定义,像这个程序的第二行会造成错误。希望你只把它当作一个例子。

    方法的定义

    即便定义了类,没有定义方法也是没有意义的。让我们试着在类C中定义方法。

    Ruby代码 复制代码 收藏代码
    1. class C   
    2.   def myupcase( str )   
    3.     return str.upcase()   
    4.   end  
    5. end  


    定义方法用def语句。这个例子中定义了myupcase方法。有一个名为str的参数。同变量一样,参数和返回值都不需要写类型。而且可以有多个参数。

    试着用一下定义的方法。缺省情况下,方法可以在外部调用。

    Ruby代码 复制代码 收藏代码
    1. c = C.new()   
    2. result = c.myupcase("content")   
    3. p(result)   # 显示"CONTENT"   


    当然,习惯之后便无需一个个的赋值了。下面的写法也是一样的。

    Ruby代码 复制代码 收藏代码
    1. p(C.new().myupcase("content"))   # 同样显示"CONTENT"   


    self

    在方法执行过程中,通常会保留自己(方法调用的实例)是谁的信息,这个信息可以通过self得到。类似于C++或Java中的this。我们来确认一下。

    Ruby代码 复制代码 收藏代码
    1. class C   
    2.   def get_self()   
    3.     return self  
    4.   end  
    5. end  
    6.   
    7. c = C.new()   
    8. p(c)              # #<C:0x40274e44>   
    9. p(c.get_self())   # #<C:0x40274e44>  


    如你所见,两个表达式返回的是同样的对象。可以确认,对c调用方法,其self就是c。

    那么,通过自身调用方法该怎么做才好呢?首先要考虑通过self进行调用。

    Ruby代码 复制代码 收藏代码
    1. class C   
    2.   def my_p( obj )   
    3.     self.real_my_p(obj)   # 通过自身调用方法   
    4.   end  
    5.   
    6.   def real_my_p( obj )   
    7.     p(obj)   
    8.   end  
    9. end  
    10. C.new().my_p(1)   # 显示1  


    不过,调用“自己的”方法还要特意指定,太麻烦。因此,对self的调用可以省略调用方法的目标对象(接收者,receiver)。

    Ruby代码 复制代码 收藏代码
    1. class C   
    2.   def my_p( obj )   
    3.     real_my_p(obj)   # 可以不指定调用的接收者   
    4.   end  
    5.   
    6.   def real_my_p( obj )   
    7.     p(obj)   
    8.   end  
    9. end  
    10.   
    11. C.new().my_p(1)   # 显示1  


    实例变量

    还有一种说法,对象是数据 + 代码,所以,仅仅定义方法还是没什么用。有必要以对象为单位来记住数据,也就是实例变量,在C++中称为成员变量。

    根据Ruby的变量命名规则,第一个字母决定类型。实例变量是“@”。

    Ruby代码 复制代码 收藏代码
    1. class C   
    2.   def set_i(value)   
    3.     @i = value   
    4.   end  
    5.   
    6.   def get_i()   
    7.     return @i  
    8.   end  
    9. end  
    10.   
    11. c = C.new()   
    12. c.set_i("ok")   
    13. p(c.get_i())   # 显示"ok"   


    实例变量不同于前面介绍的变量,即便不赋值(甚至不定义)也一样可以访问。这种情况下会变成怎样呢……接着前面的代码继续尝试。

    Ruby代码 复制代码 收藏代码
    1. c = C.new()   
    2. p(c.get_i())   # 显示nil  


    没有set就get,结果显示nil。nil表示一个“没有”的对象。存在对象却“没有”,很不可思议,没办法,它就是这样。

    nil也可以作为一个字面量使用。

    Ruby代码 复制代码 收藏代码
    1. p(nil)   # 显示nil  


    initialize

    正如我们看到的,即便是刚刚定义的类也可以调用new方法创建实例。的确如此,不过,有时需要对一个类进行特定的初始化。这时要修改的不是new方法,而是一个名为initialize的方法。它会在new的过程中调用。

    Ruby代码 复制代码 收藏代码
    1. class C   
    2.   def initialize()   
    3.     @i = "ok"    
    4.   end  
    5.   def get_i()   
    6.     return @i  
    7.   end  
    8. end  
    9. c = C.new()   
    10. p(c.get_i())   # 显示"ok"   
  • GOG-groovy on grails vs ROR- ruby on rails

    2011-04-30 00:10:43

     

    groovy参考: groovy.rar(1.15 MB)

    ROR资源列表

    http://www.iteye.com/topic/33776

    来源于  Ruby On Rails 中文社区论坛

    <入门级教程>
    Ruby On Rails入门的中文教材
    http://www.railscn.com/viewtopic.php?t=8&sid=7558f51c768090c52947e01c1dc8885c

    Rolling with Ruby on Rails.chm入门级教程下载
    http://www.railscn.com/viewtopic.php?t=557&sid=7558f51c768090c52947e01c1dc8885c

    RubyCourse_1.0-1.pdf
    http://www.railscn.com/viewtopic.php?t=562&sid=7558f51c768090c52947e01c1dc8885c

    <综合教程>---推荐
    Rails Recipes
    http://www.railscn.com/viewtopic.php?t=634&sid=7558f51c768090c52947e01c1dc8885c

    Ruby for Rails
    http://www.railscn.com/viewtopic.php?t=620&sid=7558f51c768090c52947e01c1dc8885c

    Best of Ruby Quiz
    http://www.railscn.com/viewtopic.php?t=498&sid=7558f51c768090c52947e01c1dc8885c

    Agile Web Development with Rails
    英文版教程http://www.railscn.com/viewtopic.php?t=163&sid=7558f51c768090c52947e01c1dc8885c
    中文版教程http://my4java.itpub.net/category/9983/23097
    省墨打印版http://www.railscn.com/viewtopic.php?t=542&sid=7558f51c768090c52947e01c1dc8885c

    Ruby Hacking Guide
    http://www.railscn.com/viewtopic.php?t=565&sid=7558f51c768090c52947e01c1dc8885c

    Enterprise Integration with Ruby
    http://www.railscn.com/viewtopic.php?t=539&sid=7558f51c768090c52947e01c1dc8885c

    Programming Ruby 2nd edition
    http://www.railscn.com/viewtopic.php?t=115&sid=e9def3ef4bbbeafb16bcb8c8da5931f0

    Ruby In A Nutshell
    http://www.railscn.com/viewtopic.php?t=143&sid=7558f51c768090c52947e01c1dc8885c


    <实例教程>
    Flex 和 ROR 结合应用的教程
    http://www.railscn.com/about660.html

    一个结合flash,rails和ajax一起的购物车程序(教程)
    http://www.railscn.com/viewtopic.php?t=639&sid=7558f51c768090c52947e01c1dc8885c

    12个顶尖实例
    http://oio.zzdragon.name/?p=98

    高级Rails ajax教程
    http://www.railscn.com/viewtopic.php?t=621&sid=1777733ef6a90f744b9fdc3361c81d96


    <手册>
    rails1.1新特性介绍
    http://www.railscn.com/viewtopic.php?t=522&sid=7558f51c768090c52947e01c1dc8885c

    rails 1.1 api chm
    http://www.railscn.com/viewtopic.php?t=552&sid=7558f51c768090c52947e01c1dc8885c

    Ruby-Library-QuickRef
    http://www.railscn.com/viewtopic.php?t=564&sid=7558f51c768090c52947e01c1dc8885c

    Understanding Ruby's Object Model
    http://www.railscn.com/viewtopic.php?t=563&sid=7558f51c768090c52947e01c1dc8885c

    <论坛>
    http://www.railscn.com
    http://chinaonrails.com
    http://forum.javaeye.com
    http://rorwiki.hellopen.net

    <博客>
    铁道播客:有诸多视频教程
    http://rorcast.blogger2blogger.com/

    my4java的文字博客:有很多教程的译稿、笔记等
    http://my4java.itpub.net/

    cnruby道喜技术日记:众多实例教程
    http://www.hhtong.com/blog1/

    caphe的博客
    http://www.blogjava.net/cap

    Caiwangqin的博客
    http://www.uuzone.com/blog/uu_1115110/

    <工具使用>
    rails的IDE讨论
    http://www.railscn.com/viewtopic.php?t=3&sid=7558f51c768090c52947e01c1dc8885c

    rails的IDE讨论另一篇
    http://www.railscn.com/viewtopic.php?t=510&sid=7558f51c768090c52947e01c1dc8885c

    JEDIT与ror的讨论
    http://www.railscn.com/viewtopic.php?t=518&sid=7558f51c768090c52947e01c1dc8885c

    komodo
    http://www.railscn.com/viewtopic.php?t=310&sid=7558f51c768090c52947e01c1dc8885c

    dreamweaver的可下rubyweaver
    http://www.railscn.com/viewtopic.php?t=451&sid=7558f51c768090c52947e01c1dc8885c


    <环境设置>
    ROR with Lighttpd+SCGI on Windows
    http://www.railscn.com/viewtopic.php?t=348&sid=7558f51c768090c52947e01c1dc8885c

    ROR with Lighttpd问题
    http://www.railscn.com/viewtopic.php?t=618&sid=7558f51c768090c52947e01c1dc8885c

    lighttpd+scgi 多域名配置
    http://www.railscn.com/viewtopic.php?t=471&sid=7558f51c768090c52947e01c1dc8885c

    大负荷下apache+fastcgi出错问题,
    http://www.railscn.com/viewtopic.php?t=589&sid=7558f51c768090c52947e01c1dc8885c

    装fastCGI+MySQL驱动时候的问题
    http://www.railscn.com/viewtopic.php?t=566&sid=7558f51c768090c52947e01c1dc8885c

    typo的安装问题
    http://www.railscn.com/viewtopic.php?t=633&sid=7558f51c768090c52947e01c1dc8885c

    Getting start rails' - 在DreamHost上建Typo
    http://www.railscn.com/viewtopic.php?t=377&sid=7558f51c768090c52947e01c1dc8885c

    ROR性能测试
    http://www.railscn.com/viewtopic.php?t=454&sid=7558f51c768090c52947e01c1dc8885c

    Memcache-client for Ruby On Rails
    http://www.railscn.com/viewtopic.php?t=394&sid=7558f51c768090c52947e01c1dc8885c

    <空间申请>
    free hosting ROR
    http://www.railscn.com/about703.html

    HostingRail.com - 免费Ruby on Rails空间
    http://www.railscn.com/viewtopic.php?t=628&sid=7558f51c768090c52947e01c1dc8885c

    免费的webhosting-ror 100mb
    http://www.railscn.com/viewtopic.php?t=504&sid=7558f51c768090c52947e01c1dc8885c

    国内的Ruby on Rails空间
    http://www.railscn.com/viewtopic.php?t=285&sid=7558f51c768090c52947e01c1dc8885c


    <真实应用>
    中文站点
    http://www.railscn.com/about670.html
    http://www.railscn.com/viewtopic.php?
    http://www.railscn.com/about337.html
    外国站点
    http://www.railscn.com/viewtopic.php?t=505&sid=7558f51c768090c52947e01c1dc8885c


    <杂类>
    框架比较J2EE, Rails, Zope(and more..) 视频
    http://www.railscn.com/viewtopic.php?t=641&sid=7558f51c768090c52947e01c1dc8885c

    groovy on rails
    http://www.railscn.com/viewtopic.php?t=599&sid=7558f51c768090c52947e01c1dc8885c

    用户登录引擎(共两部分)
    http://www.railscn.com/viewtopic.php?t=434&highlight=login&sid=6d906cbcb22afe0eda158d772b0a72c2
    http://www.railscn.com/viewtopic.php?t=435&highlight=login&sid=6d906cbcb22afe0eda158d772b0a72c2

    如何开启一个后台任务?railscron
    http://www.railscn.com/viewtopic.php?t=623&sid=7558f51c768090c52947e01c1dc8885c

    如何上传一个文件
    http://www.railscn.com/viewtopic.php?t=635&sid=7558f51c768090c52947e01c1dc8885c

    如何更改首页
    http://www.railscn.com/viewtopic.php?t=409&sid=7558f51c768090c52947e01c1dc8885c

     

     

  • 超级实用且不花哨的js代码大全

    2011-04-14 16:27:10

     

    事件源对象
    event.srcElement.tagName
    event.srcElement.type
    捕获释放
    event.srcElement.setCapture();
    event.srcElement.releaseCapture(); 
    事件按键
    event.keyCode
    event.shiftKey
    event.altKey
    event.ctrlKey
    事件返回值
    event.returnValue
    鼠标位置
    event.x
    event.y
    窗体活动元素
    document.activeElement
    绑定事件
    document.captureEvents(Event.KEYDOWN);
    访问窗体元素
    document.all("txt").focus();
    document.all("txt").select();
    窗体命令
    document.execCommand
    窗体COOKIE
    document.cookie
    菜单事件
    document.oncontextmenu
    创建元素
    document.createElement("SPAN"); 
    根据鼠标获得元素:
    document.elementFromPoint(event.x,event.y).tagName=="TD
    document.elementFromPoint(event.x,event.y).appendChild(ms) 
    窗体图片
    document.images[索引]
    窗体事件绑定
    document.onmousedown=scrollwindow;
    元素
    document.窗体.elements[索引]
    对象绑定事件
    document.all.xxx.detachEvent('onclick',a);
    插件数目
    navigator.plugins
    取变量类型
    typeof($js_libpath) == "undefined"
    下拉框
    下拉框.options[索引]
    下拉框.options.length
    查找对象
    document.getElementsByName("r1");
    document.getElementById(id);
    定时
    timer=setInterval('scrollwindow()',delay);
    clearInterval(timer);
    UNCODE编码
    escape() ,unescape
    父对象
    obj.parentElement(dhtml)
    obj.parentNode(dom)
    交换表的行
    TableID.moveRow(2,1)
    替换CSS
    document.all.csss.href = "a.css";
    并排显示
    display:inline
    隐藏焦点
    hidefocus=true
    根据宽度换行
    style="word-break:break-all"
    自动刷新
    <meta. HTTP-EQUIV="refresh" C>
    简单邮件
    <a  href="[email=aaa@bbb.com?subject=ccc&body=xxxyyy]mailto:aaa@bbb.com?subject=ccc&body=xxxyyy[/email]"> 
    快速转到位置
    obj.scrollIntoView(true)

    <a name="first">
    <a href="#first">anchors</a>
    网页传递参数
    location.search();
    可编辑
    obj.contenteditable=true
    执行菜单命令
    obj.execCommand
    双字节字符
    /[^\x00-\xff]/
    汉字
    /[\u4e00-\u9fa5]/
    让英文字符串超出表格宽度自动换行
    word-wrap: break-word; word-break: break-all;
    透明背景
    <IFRAME. src="1.htm" width=300 height=180 allowtransparency></iframe>
    获得style内容
    obj.style.cssText
    HTML标签
    document.documentElement.innerHTML
    第一个style标签
    document.styleSheets[0]
    style标签里的第一个样式
    document.styleSheets[0].rules[0]
    防止点击空链接时,页面往往重置到页首端。
    <a href="javascript.:function()">word</a>
    上一网页源
    asp:
    request.servervariables("HTTP_REFERER")
    javascript.
    document.referrer
    释放内存
    CollectGarbage();
    禁止右键
    document.oncontextmenu = function() { return false;}
    禁止保存
    <noscript><iframe. src="*.htm"></iframe></noscript>
    禁止选取<body      Shortcut Icon" href="favicon.ico">
    favicon.ico 名字最好不变16*16的16色,放虚拟目录根目录下
    收藏栏图标
    <link rel="Bookmark" href="favicon.ico">
    查看源码
    <input type=button value=查看网页源代码 >
    关闭输入法
    <input style="ime-mode:disabled">
    自动全选
    <input type=text name=text1 value="123" >
    ENTER键可以让光标移到下一个输入框
    <input >
    文本框的默认值
    <input type=text value="123" >
    title换行
    obj.title = "123 sdfs "
    获得时间所代表的微秒
    var n1 = new Date("2004-10-10".replace(/-/g, "\/")).getTime()
    窗口是否关闭
    win.closed

    checkbox扁平
    <input type=checkbox style="position: absolute; clip:rect(5px 15px 15px 5px)"><br>
    获取选中内容
    document.selection.createRange().duplicate().text
    自动完成功能
    <input  type=text  autocomplete=on>打开该功能
    <input  type=text  autocomplete=off>关闭该功能
    关闭窗口
    window.close();
    返回
    history.back();
    无关闭按钮IE window.open("aa.htm", "meizz", "fullscreen=7");
    统一编码/解码 alert(decodeURIComponent(encodeURIComponent("http://你好.com?as= hehe")))
    encodeURIComponent对":"、"/"、";" 和 "?"也编码
    高级应用(一)

    页面跳转:
      window.location.href('地址')
      window.open('地址', '_self')
    打开新窗口:
      window.open('地址', '_blank')
      只显示地址栏:window.open('地址', 'title', 'location=1, height=200, width=500')
      只显示状态栏:window.open('地址', 'title', 'status=1')
      只显示工具栏:window.open('地址', 'title', 'toolbar=1')
      只显示菜单栏:window.open('地址', 'title', 'menubar=1')
      一个不少:window.open('地址', 'title)
      光棍但可以调整大小:window.open('地址', 'title', 'resizable=1')
    去掉所有空格:
      Object.replace(/^\s+|\s+$/g, "")
    屏蔽鼠标:
      oncontextmenu="window.event.returnValue=false"
    取消选取:
      onselectstart="return false"
    不允许粘贴:
      onpaste="return false"
    关闭输入法:
      <input style="ime-mode:disabled">
    不允许另存为:
      <noscript><iframe. src=*.html></iframe></noscript>
    得到上一页来源:
      document.referrer
    弹出窗口总在最上:
      <body >
    弹出确认对话框:
      comfirm('文本')"
      <script>if(confirm('文本')){//确定}else{//取消}
    回车转换Tab键:
      if(window.event.keyCode==13){event.keyCode=9}
    返回上一页:
      history.go(-1)
    重新加载页面:
      window.location.reload()
    子页面中调父页面中的方法:
      window.opener.function()
    子页面中访问父页面中名为name的控件值:
      window.opener.name.value
    子页面中访问父页面中表单中名为name的控件值:
      window.opener.formName.nam.value
    得到控件的绝对位置:
      function getIE() {
        var t = e.offsetTop;
        var l = e.offsetLeft;
        while (e = e.offsetParent) {
          t += e.offsetTop;
          l += e.offsetLeft;
        }
        alert("top=" + t + " and left=" + l);
      }
    光标停在文本框文字的最后:
      function toEnd() {
        var e = event.srcElement;
        var r = e.createTextRange();
        r.moveStart("character", e.value.length);
        r.collapse(true);
        r.select();
      }
      <input type="text" value="end" >
    屏蔽功能键(Shift, Alt, Ctrl)
      function testKey() {
        if (event.shiftKey) {  // altKey; ctrlKey
          alert("Shift");
        }
      }
    不要滚动条:
      <body scroll="no">
    让竖条没有:
      <body style="overflow:scroll; overflow-y: hidden">
    让横条没有:
      <body style="overflow:scroll; overflow-x: hidden">
    去掉图片链接点击后图片周围的虚线:
      <a href="#" ><img src="test.jpg"></a>
    在子窗体中刷新父窗体:
      window.opener.location.reload()
    设置打开窗口的大小:
      <body 100)">
    设置打开窗口的位置:
      <body 100)">
    得到窗体大小:
      document.body.clientWidth;
      document.body.clientHeight;
    TEXTAREA自适应文字行数:
      <textarea rows="1" cols="17" npropertychange="this.style.posHeight=this.scrollHeight></textarea>
    屏蔽脚本错误:
      function killErrors() {
        return true;
      }
      window.onerror=killErrors();
    判断是否是字符:
      if (/[^/x00-/xff]/g.test(str)) {
        alert("有汉字");
      } else {
        alert("全是字符");
      }
     
    screen.属性:
      availHeight 获取系统屏幕的工作区域高度,排除 Microsoft&reg; Windows&reg; 任务栏。
      availWidth 获取系统屏幕的工作区域宽度,排除 Windows 任务栏。
      bufferDepth 设置或获取用于画面外位图缓冲颜色的每像素位数。
      colorDepth 获取用于目标设置或缓冲区的颜色每像素位数。
      deviceXDPI 设置或获取系统屏幕水平每英寸点数(DPI)的数值。
      deviceYDPI 设置或获取系统屏幕垂直每英寸点数(DPI)的数值。
      fontSmoothingEnabled 获取用户是否在控制面板的显示设置中启用了圆整屏幕字体边角的选项。
      height 获取屏幕的垂直分辨率。
      logicalXDPI 获取系统屏幕水平每英寸点数(DPI)的常规数值。
      logicalYDPI 获取系统屏幕垂直每英寸点数(DPI)的常规数值。
      updateInterval 设置或获取屏幕的更新间隔。
      width 获取屏幕的垂直分辨率。
     
    得到div的height值:
      div.offsetHeight(带滚动条的完整高度)
      div.clientHeight(内容的高度)

    //各种尺寸

    s  +=  "\r\n网页可见区域宽:"+  document.body.clientWidth; 
    s  +=  "\r\n网页可见区域高:"+  document.body.clientHeight; 
    s  +=  "\r\n网页可见区域高:"+  document.body.offsetWeight  +"  (包括边线的宽)"; 
    s  +=  "\r\n网页可见区域高:"+  document.body.offsetHeight  +"  (包括边线的宽)"; 
    s  +=  "\r\n网页正文全文宽:"+  document.body.scrollWidth; 
    s  +=  "\r\n网页正文全文高:"+  document.body.scrollHeight; 
    s  +=  "\r\n网页被卷去的高:"+  document.body.scrollTop; 
    s  +=  "\r\n网页被卷去的左:"+  document.body.scrollLeft; 
    s  +=  "\r\n网页正文部分上:"+  window.screenTop; 
    s  +=  "\r\n网页正文部分左:"+  window.screenLeft; 
    s  +=  "\r\n屏幕分辨率的高:"+  window.screen.height; 
    s  +=  "\r\n屏幕分辨率的宽:"+  window.screen.width; 
    s  +=  "\r\n屏幕可用工作区高度:"+  window.screen.availHeight; 
    s  +=  "\r\n屏幕可用工作区宽度:"+  window.screen.availWidth; 

     

    //过滤数字

    <input type=text nkeypress="return event.keyCode>=48&&event.keyCode<=57||(this.value.indexOf('.')<0?event.keyCode==46:false)" npaste="return !clipboardData.getData('text').match(/\D/)" ndragenter="return false">


    //特殊用途

    <input type=button value=导入收藏夹 nclick="window.external.ImportExportFavorites(true,'http://localhost');">
    <input type=button value=导出收藏夹 nclick="window.external.ImportExportFavorites(false,'http://localhost');">
    <input type=button value=整理收藏夹 nclick="window.external.ShowBrowserUI('OrganizeFavorites', null)">
    <input type=button value=语言设置   nclick="window.external.ShowBrowserUI('LanguageDialog', null)">
    <input type=button value=加入收藏夹 nclick="window.external.AddFavorite('http://www.google.com/', 'google')">
    <input type=button value=加入到频道 nclick="window.external.addChannel('http://www.google.com/')">
    <input type=button value=加入到频道 nclick="window.external.showBrowserUI('PrivacySettings',null)">


    //不缓存

    <META. HTTP-EQUIV="pragma" CONTENT="no-cache">
    <META. HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate">
    <META. HTTP-EQUIV="expires" CONTENT="0">


    //正则匹配

    匹配中文字符的正则表达式: [\u4e00-\u9fa5]
    匹配双字节字符(包括汉字在内):[^\x00-\xff]
    匹配空行的正则表达式:\n[\s| ]*\r
    匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/
    匹配首尾空格的正则表达式:(^\s*)|(\s*$)(像vbscript那样的trim函数)
    匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
    匹配网址URL的正则表达式:http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
    以下是例子:
    利用正则表达式限制网页表单里的文本框输入内容:
    用正则表达式限制只能输入中文:onkeyup="value=value.replace(/[^\u4E00-\u9FA5]/g,'')" nbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\u4E00-\u9FA5]/g,''))"
    1.用正则表达式限制只能输入全角字符: nkeyup="value=value.replace(/[^\uFF00-\uFFFF]/g,'')" nbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\uFF00-\uFFFF]/g,''))"
    2.用正则表达式限制只能输入数字:onkeyup="value=value.replace(/[^\d]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"
    3.用正则表达式限制只能输入数字和英文:onkeyup="value=value.replace(/[\W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"


    //消除图像工具栏

    <IMG SRC="mypicture.jpg" HEIGHT="100px" WIDTH="100px" GALLERYIMG="false">
    or
    <head>
    <meta. http-equiv="imagetoolbar" content="no">
    </head>


    //无提示关闭

    function Close()
    {
    var ua=navigator.userAgent
    var ie=navigator.appName=="Microsoft Internet Explorer"?true:false
    if(ie)
    {
          var IEversion=parseFloat(ua.substring(ua.indexOf("MSIE ")+5,ua.indexOf(";",ua.indexOf("MSIE "))))
      if(IEversion< 5.5)
      {
       var str  = '<object id=noTipClose classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">'
           str += '<param name="Command" value="Close"></object>';
           document.body.insertAdjacentHTML("beforeEnd", str);
           document.all.noTipClose.Click();
      }
          else
      {
           window.opener =null;
           window.close();
          }
       }
    else
    {
      window.close()
       }
    }

     

    //取得控件得绝对位置(1)

    <script. language="javascript">
    function getoffset(e)
    {
    var t=e.offsetTop;
    var l=e.offsetLeft;
    while(e=e.offsetParent)
    {
      t+=e.offsetTop;
      l+=e.offsetLeft;
    }
    var rec = new Array(1);
    rec[0]  = t;
    rec[1] = l;
    return rec
    }
    </script>

     

    //获得控件的绝对位置(2)

    oRect = obj.getBoundingClientRect();
    oRect.left
    oRect.


    //最小化,最大化,关闭

    <object id=min classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">
    <param name="Command" value="Minimize"></object>
    <object id=max classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">
    <param name="Command" value="Maximize"></object>
    <OBJECT id=close classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11">
    <PARAM NAME="Command" value="Close"></OBJECT>
    <input type=button value=最小化 nclick=min.Click()>
    <input type=button value=最大化 nclick=max.Click()>
    <input type=button value=关闭 nclick=close.Click()>


    //光标停在文字最后


    <script. language="javascript">
    function cc()
    {
    var e = event.srcElement;
    var r =e.createTextRange();
    r.moveStart('character',e.value.length);
    r.collapse(true);
    r.select();
    }
    </script>
    <input type=text name=text1 value="123" nfocus="cc()">

    //页面进入和退出的特效

    进入页面<meta. http-equiv="Page-Enter" content="revealTrans(duration=x, transition=y)">
    推出页面<meta. http-equiv="Page-Exit" content="revealTrans(duration=x, transition=y)">
    这个是页面被载入和调出时的一些特效。duration表示特效的持续时间,以秒为单位。transition表示使
    用哪种特效,取值为1-23:
      0 矩形缩小
      1 矩形扩大
      2 圆形缩小
      3 圆形扩大
      4 下到上刷新
      5 上到下刷新
      6 左到右刷新
      7 右到左刷新
      8 竖百叶窗
      9 横百叶窗
      10 错位横百叶窗
      11 错位竖百叶窗
      12 点扩散
      13 左右到中间刷新
      14 中间到左右刷新
      15 中间到上下
      16 上下到中间
      17 右下到左上
      18 右上到左下
      19 左上到右下
      20 左下到右上
      21 横条
      22 竖条
      23


    //网页是否被检索 <meta. name="ROBOTS" content="属性值">
      其中属性值有以下一些:
      属性值为"all": 文件将被检索,且页上链接可被查询;
      属性值为"none": 文件不被检索,而且不查询页上的链接;
      属性值为"index": 文件将被检索;
      属性值为"follow": 查询页上的链接;
      属性值为"noindex": 文件不检索,但可被查询链接;
      属性值为"nofollow":


    //打印分页 <p  style="page-break-after:always">page1</p> 
    <p  style="page-break-after:always">page2</p> 

     

    //设置打印

    <object id="factory" style="display:none" viewastext
      classid="clsid:1663ed61-23eb-11d2-b92f-008048fdd814"
      codebase="http://www.meadroid.com/scriptx/ScriptX.cab#Version=5,60,0,360"
    ></object>
    <input type=button value=页面设置 nclick="factory.printing.PageSetup()">
    <input type=button value=打印预览 nclick="factory.printing.Preview()">
     
    <script. language=javascript>
    function window.onload()
    {
       // -- advanced features
       factory.printing.SetMarginMeasure(2) // measure margins in inches
       factory.printing.SetPageRange(false, 1, 3) // need pages from 1 to 3
       factory.printing.printer = "HP DeskJet 870C"
       factory.printing.copies = 2
       factory.printing.collate = true
       factory.printing.paperSize = "A4"
       factory.printing.paperSource = "Manual feed"
       // -- basic features
       factory.printing.header = "居左显示&b居中显示&b居右显示页码,第&p页/共&P页"
       factory.printing.footer = "(自定义页脚)"
       factory.printing.portrait = false
       factory.printing.leftMargin = 0.75
       factory.printing.topMargin = 1.5
       factory.printing.rightMargin = 0.75
       factory.printing.bottomMargin = 1.5
    }
    function Print(frame) {
      factory.printing.Print(true, frame) // print with prompt
    }
    </script>
    <input type=button value="打印本页" nclick="factory.printing.Print(false)">
    <input type=button value="页面设置" nclick="factory.printing.PageSetup()">
    <input type=button value="打印预览" nclick="factory.printing.Preview()"><br>
    <a href="http://www.meadroid.com/scriptx/docs/printdoc.htm?static"  target=_blank>具体使用手册,更多信息,点这里</a>


    //自带的打印预览
    WebBrowser.ExecWB(1,1) 打开
    Web.ExecWB(2,1) 关闭现在所有的IE窗口,并打开一个新窗口
    Web.ExecWB(4,1) 保存网页
    Web.ExecWB(6,1) 打印
    Web.ExecWB(7,1) 打印预览
    Web.ExecWB(8,1) 打印页面设置
    Web.ExecWB(10,1) 查看页面属性
    Web.ExecWB(15,1) 好像是撤销,有待确认
    Web.ExecWB(17,1) 全选
    Web.ExecWB(22,1) 刷新
    Web.ExecWB(45,1) 关闭窗体无提示
    <style. media=print>
    .Noprint{display:none;}<!--用本样式在打印时隐藏非打印项目-->
    .PageNext{page-break-after: always;}<!--控制分页-->
    </style>
    <object  id="WebBrowser"  width=0  height=0  classid="CLSID:8856F961-340A-11D0-A96B-00C04FD705A2">   
    </object>   
     
    <center class="Noprint" >
    <input type=button value=打印 nclick=document.all.WebBrowser.ExecWB(6,1)>
    <input type=button value=直接打印 nclick=document.all.WebBrowser.ExecWB(6,6)>
    <input type=button value=页面设置 nclick=document.all.WebBrowser.ExecWB(8,1)>
    </p>
    <p> <input type=button value=打印预览 nclick=document.all.WebBrowser.ExecWB(7,1)>
    </center>

     

    //去掉打印时的页眉页脚

    <script  language="JavaScript"> 
    var HKEY_Root,HKEY_Path,HKEY_Key;
    HKEY_Root="HKEY_CURRENT_USER";
    HKEY_Path="\\Software\\Microsoft\\Internet Explorer\\PageSetup\\";
    //设置网页打印的页眉页脚为空
    function PageSetup_Null()
    {
    try
    {
      var Wsh=new ActiveXObject("WScript.Shell");
      HKEY_Key="header";
      Wsh.RegWrite(HKEY_Root+HKEY_Path+HKEY_Key,"");
      HKEY_Key="footer";
      Wsh.RegWrite(HKEY_Root+HKEY_Path+HKEY_Key,"");
    }
    catch(e){}
    }
    //设置网页打印的页眉页脚为默认值
    function  PageSetup_Default()

    try
    {
      var Wsh=new ActiveXObject("WScript.Shell");
      HKEY_Key="header";
      Wsh.RegWrite(HKEY_Root+HKEY_Path+HKEY_Key,"&w&b页码,&p/&P");
      HKEY_Key="footer";
      Wsh.RegWrite(HKEY_Root+HKEY_Path+HKEY_Key,"&u&b&d");
    }
    catch(e){}
    }
    </script>
    <input type="button" value="清空页码" nclick=PageSetup_Null()>
    <input type="button" value="恢复页码" nclick=PageSetup_Default()>


    //浏览器验证

    function checkBrowser()
    {
       this.ver=navigator.appVersion
       this.dom=document.getElementById?1:0
       this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom)?1:0;
       this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom)?1:0;
       this.ie4=(document.all && !this.dom)?1:0;
       this.ns5=(this.dom && parseInt(this.ver) >= 5) ?1:0;
       this.ns4=(document.layers && !this.dom)?1:0;
       this.mac=(this.ver.indexOf('Mac') > -1) ?1:0;
       this.ope=(navigator.userAgent.indexOf('Opera')>-1);
       this.ie=(this.ie6 || this.ie5 || this.ie4)
       this.ns=(this.ns4 || this.ns5)
       this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns5 || this.ns4 || this.mac || this.ope)
       this.nbw=(!this.bw)
       return this;
    }


    //计算内容宽和高

    <SCRIPT  language="javascript"> 
    function  test(obj) 

           var  range  =  obj.createTextRange(); 
           alert("内容区宽度:  "  +  range.boundingWidth
                 +  "px\r\n内容区高度:  "  +  range.boundingHeight  +  "px"); 

    </SCRIPT> 
    <BODY> 
    <Textarea id="txt" height="150">sdf</textarea><INPUT  type="button"  value="计算内容宽度"  nClick="test(txt)"> 
    </BODY>

     

    //无模式的提示框 function modelessAlert(Msg)
    {
       window.showModelessDialog("javascript.:alert(\""+escape(Msg)+"\");window.close();","","status:no;resizable:no;help:no;dialogHeight:height:30px;dialogHeight:40px;");
    }

     

    //屏蔽按键
    <html>
    <head>
      <meta. http-equiv="Content-Type" content="text/html; charset=gb2312">
      <noscript><meta. http-equiv="refresh" content="0;url=about:noscript"></noscript>
      <title>屏蔽鼠标右键、Ctrl+N、Shift+F10、Alt+F4、F11、F5刷新、退格键</title>
    </head>
    <body>
    <script. language="Javascript"><!--
      //屏蔽鼠标右键、Ctrl+N、Shift+F10、F11、F5刷新、退格键
      //Author: meizz(梅花雨) 2002-6-18
    function document.oncontextmenu(){event.returnValue=false;}//屏蔽鼠标右键
    function window.onhelp(){return false} //屏蔽F1帮助
    function document.onkeydown()
    {
      if ((window.event.altKey)&&
          ((window.event.keyCode==37)||   //屏蔽 Alt+ 方向键 ←
           (window.event.keyCode==39)))   //屏蔽 Alt+ 方向键 →
      {
         alert("不准你使用ALT+方向键前进或后退网页!");
         event.returnValue=false;
      }
         /* 注:这还不是真正地屏蔽 Alt+ 方向键,
         因为 Alt+ 方向键弹出警告框时,按住 Alt 键不放,
         用鼠标点掉警告框,这种屏蔽方法就失效了。以后若
         有哪位高手有真正屏蔽 Alt 键的方法,请告知。*/
      if ((event.keyCode==8)  ||                 //屏蔽退格删除键
          (event.keyCode==116)||                 //屏蔽 F5 刷新键
          (event.ctrlKey && event.keyCode==82)){ //Ctrl + R
         event.keyCode=0;
         event.returnValue=false;
         }
      if (event.keyCode==122){event.keyCode=0;event.returnValue=false;}  //屏蔽F11
      if (event.ctrlKey && event.keyCode==78) event.returnValue=false;   //屏蔽 Ctrl+n
      if (event.shiftKey && event.keyCode==121)event.returnValue=false;  //屏蔽 shift+F10
      if (window.event.srcElement.tagName == "A" && window.event.shiftKey)
          window.event.returnValue = false;             //屏蔽 shift 加鼠标左键新开一网页
      if ((window.event.altKey)&&(window.event.keyCode==115))             //屏蔽Alt+F4
      {
          window.showModelessDialog("about:blank","","dialogWidth:1px;dialogheight:1px");
          return false;
      }
    }
    </script>
    屏蔽鼠标右键、Ctrl+N、Shift+F10、Alt+F4、F11、F5刷新、退格键
    </body>
    </html>


    //屏蔽打印
    <style>
    @media print{
    * {display:none}
    }
    </style>


    //移动的图层,拖动

    1.<span style='position:absolute;width:200;height:200;background:red' nmousedown=MouseDown(this) nmousemove=MouseMove() nmouseup=MouseUp()>meizz</span>
    <script. language=javascript>
    var Obj;
    function MouseDown(obj)
    {
      bj=obj;
      Obj.setCapture();
      Obj.l=event.x-Obj.style.pixelLeft;
      Obj.t=event.y-Obj.style.pixelTop;
    }
    function MouseMove()
    {
      if(Obj!=null)
      {
        Obj.style.left = event.x-Obj.l;
        Obj.style.top = event.y-Obj.t;
      }
    }
    function MouseUp()
    {
      if(Obj!=null)
      {
        Obj.releaseCapture();
        bj=null;
      }
    }
    </script>
    2.
    <div id="myDiv" src="logo.gif" ndrag="doDrag();" nmouseover="this.style.cursor='hand'" style="position:absolute;left=100;top=100;" nmousedown="doMouseDown();">
    <a href="#" nclick="return false"><h1>wlecome</h1></a>
    </div>
    <script. language="JavaScript" type="text/javascript">
    var orgMouseX;
    var orgMouseY;
    var orgObjX;
    var orgObjY;
    function doDrag()
    {
    var myObject=document.all.myDiv;

    var x=event.clientX;
    var y=event.clientY;
    myObject.style.left=x-(orgMouseX-orgObjX);
    myObject.style.top=y-(orgMouseY-orgObjY);
     
    }
    function doMouseDown()
    {
    orgMouseX=event.clientX;
    orgMouseY=event.clientY;
    orgObjX=parseInt(document.all.myDiv.style.left);
    orgObjY=parseInt(document.all.myDiv.style.top);
    }

    </script>
     
    //文档状态改变

    <iframe. src="a.html" id="f" name="f" scrolling="no" frameborder=0 marginwidth=0 marginheight=0></iframe>
    <script>
    var doc=window.frames["f"].document;
    function s(){
    if (doc.readyState=="complete"){
      document.all.f.style.height=doc.body.scrollHeight
      document.all.f.style.width=doc.body.scrollWidth
    }
    }
    doc.onreadystatechange=s
    </script>


    //刷新后不变的文本框 <HTML>
    <HEAD>
    <META. NAME="save" CONTENT="history">
    <STYLE>
       .sHistory {behavior.:url(#default#savehistory);}
    </STYLE>
    </HEAD>
    <BODY>
    <INPUT class=sHistory type=text id=oPersistInput>
    </BODY>
    </HTML>

    //访问剪贴板

    event.dataTransfer.setData("URL", oImage.src);
    sImageURL = event.dataTransfer.getData("URL")
    (2)普通访问
    window.clipboardData.setData("Text",oSource.innerText);
    window.clipboardData.getData("Text");


    //操作COOKIE

    function SetCookie(sName, sValue)
    {
    document.cookie = sName + "=" + escape(sValue) + "; ";
    }
    function GetCookie(sName)
    {
    var aCookie = document.cookie.split("; ");
    for (var i=0; i < aCookie.length; i++)
    {
     
      var aCrumb = aCookie.split("=");
      if (sName == aCrumb[0])
      return unescape(aCrumb[1]);
    }
     
    }
    function DelCookie(sName)
    {
    document.cookie = sName + "=" + escape(sValue) + "; expires=Fri, 31 Dec 1999 23:59:59 GMT;";
    }


    //setTimeout增加参数

    <script>
    var _st = window.setTimeout;
    window.setTimeout = function(fRef, mDelay) {
    if(typeof fRef == 'function'){
      var argu = Array.prototype.slice.call(arguments,2);
      var f = (function(){ fRef.apply(null, argu); });
      return _st(f, mDelay);
    }
    return _st(fRef,mDelay);
    }
    function test(x){
    alert(x);
    }
    window.setTimeout(test,1000,'fason');
    </script>


    //自定义的apply,call

    Function.prototype.apply = function (obj, argu) {
    if (obj) obj.constructor.prototype._caller = this;
    var argus = new Array();
    for (var i=0;i<argu.length;i++)
      argus = "argu[" + i + "]";
    var r;
    eval("r = " + (obj ? ("obj._caller(" + argus.join(",") + ");") : ("this(" + argus.join(",") + ");")));
    return r;
    };
    Function.prototype.call = function (obj) {
    var argu = new Array();
    for (var i=1;i<arguments.length;i++)
      argu[i-1] = arguments;
    return this.apply(obj, argu);
    };      

    //下载文件

    function DownURL(strRemoteURL,strLocalURL)
    {
    try
    {
      var xmlHTTP=new ActiveXObject("Microsoft.XMLHTTP");
      xmlHTTP.open("Get",strRemoteURL,false);
      xmlHTTP.send();
      var adodbStream=new ActiveXObject("ADODB.Stream");
      adodbStream.Type=1;//1=adTypeBinary
      adodbStream.Open();
      adodbStream.write(xmlHTTP.responseBody);
      adodbStream.SaveToFile(strLocalURL,2);
      adodbStream.Close();
      adodbStream=null;
      xmlHTTP=null;
     
    }
    catch(e)
    {
      window.confirm("下载URL出错!");
    }
    //window.confirm("下载完成.");
    }

    //检验连接是否有效

    function getXML(URL)
    {
    var xmlhttp = new ActiveXObject("microsoft.xmlhttp");
    xmlhttp.Open("GET",URL, false);
    try
    {
      xmlhttp.Send();
    }
    catch(e){}
    finally
    {
      var result = xmlhttp.responseText;
      if(result)
      {
       if(xmlhttp.Status==200)
       {
        return(true);
       }
       else
       {
        return(false);
       }
      }
      else
      {
       return(false);
      }
    }
    }

     

    //POST代替FORM.

    <SCRIPT. language="VBScript">
    Function URLEncoding(vstrIn)
        strReturn = ""
        For i = 1 To Len(vstrIn)
            ThisChr = Mid(vStrIn,i,1)
            If Abs(Asc(ThisChr)) < &HFF Then
                strReturn = strReturn & ThisChr
            Else
                innerCode = Asc(ThisChr)
                If innerCode < 0 Then
                    innerCode = innerCode + &H10000
                End If
                Hight8 = (innerCode  And &HFF00)\ &HFF
                Low8 = innerCode And &HFF
                strReturn = strReturn & "%" & Hex(Hight8) &  "%" & Hex(Low8)
            End If
        Next
        URLEncoding = strReturn
    End Function
    Function bytes2BSTR(vIn)
        strReturn = ""
        For i = 1 To LenB(vIn)
            ThisCharCode = AscB(MidB(vIn,i,1))
            If ThisCharCode < &H80 Then
                strReturn = strReturn & Chr(ThisCharCode)
            Else
                NextCharCode = AscB(MidB(vIn,i+1,1))
                strReturn = strReturn & Chr(CLng(ThisCharCode) * &H100 + CInt(NextCharCode))
                i = i + 1
            End If
        Next
        bytes2BSTR = strReturn
    End Function
    dim strA,oReq
    strA = URLEncoding("submit1=Submit&text1=中文")
    set Req = CreateObject("MSXML2.XMLHTTP")
    oReq.open "POST","http://ServerName/VDir/TstResult.asp",false
    oReq.setRequestHeader "Content-Length",Len(strA)
    oReq.setRequestHeader "CONTENT-TYPE","application/x-www-form-urlencoded"
    oReq.send strA
    msgbox bytes2BSTR(oReq.responseBody)
    </SCRIPT>

    //readyState是xmlhttp返回数据的进度,0=载入中,1=未初始化,2=已载入,3=运行中,4=完成


    高级应用(二)


    //组件是否安装

    isComponentInstalled("{6B053A4B-A7EC-4D3D-4567-B8FF8A1A5739}", "componentID"))

     

    //检查网页是否存在

    function CheckURL(URL)
    {
      var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      xmlhttp.Open("GET",URL, false);
      try
      {
        xmlhttp.Send();
        var result = xmlhttp.status;
      }
      catch(e) {return(false); }
      if(result==200)
      {
        return true;
      }
      xmlhttp = null;
      return false;
    }


    //连接数据库

    <script. language="javascript">
      //用 JavaScript. 写服务器端连接数据库的代码示例
      var conn = new ActiveXObject("ADODB.Connection");
      conn.Open("Provider=SQLOLEDB.1; Data Source=localhost; User ID=sa; "
        +"Password=; Initial Catalog=pubs");
      var rs = new ActiveXObject("ADODB.Recordset");
      var sql="select * from authors";
      rs.open(sql, conn);
    shtml = "<table width='100%' border=1>";
    shtml +="<tr bgcolor='#f4f4f4'><td>au_id</td><td>au_lname</td><td>au_fname</td><td>phone</td><td>address</td><td> city</td><td>state</td><td>zip</td></tr>";
      while(!rs.EOF)
      {
    shtml += "<tr><td>" + rs("au_id") + "</td><td>" + rs("au_lname") + "</td><td>" + rs("au_fname") + "</td><td>" + rs("phone") + "</td><td>" + rs("address") + "</td><td>" + rs("city") + "</td><td>" + rs("state") + "</td><td>" + rs("zip") + "</td></tr>";
    rs.moveNext;
      }
      shtml += "</table>";
      document.write(shtml);
      rs.close();
      rs = null;
      conn.close();
      conn = null;
    </script>


    //使用数据岛

    <html>
    <body>
    srno:<input type=text datasrc=#xmldate DataFLD=srno size="76"><BR>
    times:<input type=text datasrc=#xmldate DataFLD=times size="76"><BR>
    <input id="first" TYPE=button value="<< 第一条记录" nclick="xmldate.recordset.moveFirst()">
    <input id="prev" TYPE=button value="<上一条记录" nclick="xmldate.recordset.movePrevious()"> 
    <input id="next" TYPE=button value="下一条记录>" nclick="xmldate.recordset.moveNext()"> 
    <input id="last" TYPE=button value="最后一条记录>>" nclick="xmldate.recordset.moveLast()">  
    <input id="Add" TYPE=button value="添加新记录" nclick="xmldate.recordset.addNew()"> 

    <XML ID="xmldate">
    <infolist>
    <info ><srno>20041025-01</srno><times>null</times></info>
    <info ><srno>20041101-09</srno><times>2004年10月1日2点22分0秒</times></info>
    </infolist>
    </XML>
    </body>
    </html>


    //获得参数

    <body>
    <a href="javascript.:location.href=location.href + '?a=1&b=2'">search</a>
    <script. language="JavaScript">
    <!--
    var a = location.search.substr(1);
    if(a.length>0)
    {
    var re = /([^&]*?)\=([^&]*)/g
    var s = a.match(re);
    for(var i= 0;i<s.length;i++)
    {
      alert(s);
      alert(s.split("=")[1]);
    }
    }
    //-->
    </script>
    </body>

     

    //可编辑SELECT

    <input type=text name=re_name style="width:100px;height:21px;font-size:10pt;"><span style="width:18px;border:0px solid red;"><select name="r00" style="margin-left:-100px;width:118px; background-color:#FFEEEE;" nChange="document.all.re_name.value=this.value;">
                    <option value="1">11111111<option>
                    <option value="2">222222</option>
                    <option value="3">333333</option>
                  </select>
                  </span>

     

    //设置光标位置

    function getCaret(textbox)
    {
    var control = document.activeElement;
    textbox.focus();
    var rang = document.selection.createRange();
      rang.setEndPoint("StartToStart",textbox.createTextRange())
    control.focus();
    return rang.text.length;
    }
    function setCaret(textbox,pos)
    {
    try
    {
      var r =textbox.createTextRange();
       r.moveStart('character',pos);
       r.collapse(true);
       r.select();
    }
    catch(e)
    {}
    }
    function selectLength(textbox,start,len)
    {
    try
    {
      var r =textbox.createTextRange();
     
      r.moveEnd('character',len-(textbox.value.length-start));
      r.moveStart('character',start);
     
      r.select();
    }
    catch(e)
    {//alert(e.description)}
    }
    function insertAtCaret(textbox,text)
    {
    textbox.focus();
    document.selection.createRange().text = text;
    }

    //页内查找

    function findInPage(str)
    {
    var txt, i, found,n = 0;
    if (str == "")
    {
      return false;
    }
    txt = document.body.createTextRange();
    for (i = 0; i <= n && (found = txt.findText(str)) != false; i++)
    {
      txt.moveStart("character", 1);
      txt.moveEnd("textedit");
    }
    if (found)
    {
      txt.moveStart("character", -1);
      txt.findText(str);
      txt.select();
      txt.scrollIntoView();
      n++; 
    }
    else
    {
      if (n > 0)
      {
       n = 0;
       findInPage(str);
      }
      else
      {
       alert(str + "...  您要找的文字不存在。\n \n请试着输入页面中的关键字再次查找!");
      }
    }
    return false;
    }

     


    //操作EXECL

    <script. language="javascript">
    function jStartExcel() {
    var xls = new ActiveXObject ( "Excel.Application" );
    xls.visible = true;
    var newBook = xls.Workbooks.Add;
    newBook.Worksheets.Add;
    newBook.Worksheets(1).Activate;
    xls.ActiveWorkBook.ActiveSheet.PageSetup.Orientation = 2;
    xls.ActiveWorkBook.ActiveSheet.PageSetup.PaperSize = 5;
    newBook.Worksheets(1).Columns("A").columnwidth=50;
    newBook.Worksheets(1).Columns("A").WrapText = true;
    newBook.Worksheets(1).Columns("B").columnwidth=50;
    newBook.Worksheets(1).Columns("B").WrapText = true;
    newBook.Worksheets(1).Range("A1:B1000").NumberFormat = "0";
    newBook.Worksheets(1).Range("A1:B1000").HorizontalAlignment = -4131;
    newBook.Worksheets(1).Cells(1,1).Interior.ColorIndex="15";
    newBook.Worksheets(1).Cells(1,1).value="First Column, First Cell";
    newBook.Worksheets(1).Cells(2,1).value="First Column, Second Cell";
    newBook.Worksheets(1).Cells(1,2).value="Second Column, First Cell";
    newBook.Worksheets(1).Cells(2,2).value="Second Column, Second Cell";
    newBook.Worksheets(1).Name="My First WorkSheet";
    }
    </script>


    //自定义提示条

    <a href="#" title="这是提示">tip</a>
    <script. Language="JavaScript">
    //***********默认设置定义.*********************
    tPopWait=50;//停留tWait豪秒后显示提示。
    tPopShow=5000;//显示tShow豪秒后关闭提示
    showPopStep=20;
    popOpacity=99;
    //***************内部变量定义*****************
    sPop=null;
    curShow=null;
    tFadeOut=null;
    tFadeIn=null;
    tFadeWaiting=null;
    document.write("<style. type='text/css'id='defaultPopStyle'>");
    document.write(".cPopText {  background-color: #F8F8F5;color:#000000; border: 1px #000000 solid;font-color: font-size: 12px; padding-right: 4px; padding-left: 4px; height: 20px; padding-top: 2px; padding-bottom: 2px; filter: Alpha(Opacity=0)}");
    document.write("</style>");
    document.write("<div id='dypopLayer' style='position:absolute;z-index:1000;' class='cPopText'></div>");

    function showPopupText(){
    var o=event.srcElement;
    MouseX=event.x;
    MouseY=event.y;
    if(o.alt!=null && o.alt!=""){o.dypop=o.alt;o.alt=""};
            if(o.title!=null && o.title!=""){o.dypop=o.title;o.title=""};
    if(o.dypop!=sPop) {
    sPop=o.dypop;
    clearTimeout(curShow);
    clearTimeout(tFadeOut);
    clearTimeout(tFadeIn);
    clearTimeout(tFadeWaiting);
    if(sPop==null || sPop=="") {
    dypopLayer.innerHTML="";
    dypopLayer.style.filter="Alpha()";
    dypopLayer.filters.Alpha.opacity=0;
    }
    else {
    if(o.dyclass!=null) popStyle=o.dyclass
    else popStyle="cPopText";
    curShow=setTimeout("showIt()",tPopWait);
    }
    }
    }
    function showIt(){
    dypopLayer.className=popStyle;
    dypopLayer.innerHTML=sPop;
    popWidth=dypopLayer.clientWidth;
    popHeight=dypopLayer.clientHeight;
    if(MouseX+12+popWidth>document.body.clientWidth) popLeftAdjust=-popWidth-24
    else popLeftAdjust=0;
    if(MouseY+12+popHeight>document.body.clientHeight) popTopAdjust=-popHeight-24
    else popTopAdjust=0;
    dypopLayer.style.left=MouseX+12+document.body.scrollLeft+popLeftAdjust;
    dypopLayer.style.top=MouseY+12+document.body.scrollTop+popTopAdjust;
    dypopLayer.style.filter="Alpha(Opacity=0)";
    fadeOut();
    }
    function fadeOut(){
    if(dypopLayer.filters.Alpha.opacity<popOpacity) {
    dypopLayer.filters.Alpha.opacity+=showPopStep;
    tFadeOut=setTimeout("fadeOut()",1);
    }
    else {
    dypopLayer.filters.Alpha.opacity=popOpacity;
    tFadeWaiting=setTimeout("fadeIn()",tPopShow);
    }
    }
    function fadeIn(){
    if(dypopLayer.filters.Alpha.opacity>0) {
    dypopLayer.filters.Alpha.opacity-=1;
    tFadeIn=setTimeout("fadeIn()",1);
    }
    }
    document.onmouseover=showPopupText;
    </script>


    //插入文字

    document.onclick =function(){
    var Source = window.event.srcElement;
    if(oSource.tagName!="DIV")
    return false;
    var sel = document.selection;
    if (sel!=null) {
    var rng = sel.createRange();
    if (rng!=null)
    rng.pasteHTML("<font color=red>插入文字</font>");
    }
    }

     

    //netscapte下操作xml

    doc = new ActiveXObject("Msxml2.DOMDocument");
    doc = new ActiveXObject("Microsoft.XMLDOM")
    ->>
    doc = (new DOMParser()).parseFromString(sXML,'text/xml')


    //禁止FSO

    1.注销组件
    regsvr32 /u scrrun.dll
    2.修改PROGID
    HKEY_CLASSES_ROOT\Scripting.FileSystemObject
    Scripting.FileSystemObject
    3.对于使用object的用户,修改HKEY_CLASSES_ROOT\Scripting.


    //省略号

    <DIV STYLE="width: 120px; height: 50px; border: 1px solid blue;
                overflow: hidden; text-overflow:ellipsis">
    <NOBR>就是比如有一行文字,很长,表格内一行显示不下.</NOBR>
    </DIV>


    //判断键值

    <html>
    <meta. http-equiv="Content-Type" content="text/html; charset=gb2312">
    <head>
    <script. language="javascript">
    var ie  =navigator.appName=="Microsoft Internet Explorer"?true:false;
     
    function keyDown(e)
    {
    if(!ie)
    {
      var nkey=e.which;
      var iekey='现在是ns浏览器';
      var realkey=String.fromCharCode(e.which);
    }
    if(ie)
    {
      var iekey=event.keyCode;
      var nkey='现在是ie浏览器';
      var realkey=String.fromCharCode(event.keyCode);
      if(event.keyCode==32){realkey='\' 空格\''}
      if(event.keyCode==13){realkey='\' 回车\''}
      if(event.keyCode==27){realkey='\' Esc\''}
      if(event.keyCode==16){realkey='\' Shift\''}
      if(event.keyCode==17){realkey='\' Ctrl\''}
      if(event.keyCode==18){realkey='\' Alt\''}
    }
    alert('ns浏览器中键值:'+nkey+'\n'+'ie浏览器中键值:'+iekey+'\n'+'实际键为'+realkey);
    }
    document.onkeydown = keyDown;
    </script>
    </head>
    <body>
    //Javascript. Document.
    <hr>
    <center>
    <h3>请按任意一个键。。。。</h3>
    </center>
    </body>
    </html>

     

    //检测media play版本

    <IE:clientCaps ID="oClientCaps" style="{behavior.:url(#default#clientcaps)}" />
    <SCRIPT>
    var flash="";
        WMPVersion= oClientCaps.getComponentVersion("{22D6F312-B0F6-11D0-94AB-0080C74C7E95}","ComponentID");
        if (WMPVersion != "") {
        flash = "";
        var version = WMPVersion.split(",");
        var i;
        for (i = 0; i < version.length; i++) {
          if (i != 0)
        flash += ".";
          flash += version;
        }
         document.write("您的Windows Media Player 版本是:"+flash+"<p>");
      }
    </SCRIPT>

     

    //图象按比例

    <script. language="JavaScript">
    <!--
    //图片按比例缩放
    var flag=false;
    function DrawImage(ImgD){
    var image=new Image();
    var iwidth = 80;  //定义允许图片宽度
    var iheight = 80;  //定义允许图片高度
    image.src=ImgD.src;
    if(image.width>0 && image.height>0){
    flag=true;
    if(image.width/image.height>= iwidth/iheight){
      if(image.width>iwidth){ 
      ImgD.width=iwidth;
      ImgD.height=(image.height*iwidth)/image.width;
      }else{
      ImgD.width=image.width; 
      ImgD.height=image.height;
      }
      ImgD.alt=image.width+"×"+image.height;
      }
    else{
      if(image.height>iheight){ 
      ImgD.height=iheight;
      ImgD.width=(image.width*iheight)/image.height; 
      }else{
      ImgD.width=image.width; 
      ImgD.height=image.height;
      }
      ImgD.alt=image.width+"×"+image.height;
      }
    }
    }
    //-->
    </script>
    <img src=".." nload = "DrawImage(this)">

     

    //细线SELECT

    <span style="border:1px solid #000000; position:absolute; overflow:hidden;" >
    <select style="margin:-2px;">
    <option>1111</option>
    <option>11111111111111</option>
    <option>111111111</option>
    </select></span>


    //Import

    function Import() {
    for( var i=0; i<arguments.length; i++ ) {
      var file = arguments;
      if ( file.match(/\.js$/i))
       document.write('<script. type=\"text/javascript\" src=\"' + file + '\"></sc' + 'ript>');
      else
       document.write('<style. type=\"text/css\">@import \"' + file + '\" ;</style>');
    }
    };


    //js枚举

    function getComputerName()
    {
    var bjWMIService = GetObject("Winmgmts:root\cimv2");
    for(e = new Enumerator(objWMIService) ; !e.atEnd() ; e.moveNext())
    {
        var getComputer = e.item();
        return getComputer.Name;
    }
    }


    //条件编译

    <script. language=javascript>
    /*@cc_on @*/
    /*@if (@_win32 && @_jscript_version>5)
    function window.confirm(str)
    {
        execScript("n = msgbox('"+ str +"', 257)", "vbscript");
        return(n == 1);
    }
    @end @*/
    </script>


    //取得innerText


    <SCRIPT. LANGUAGE="JavaScript">
    <!--
    var xmlDoc = new ActiveXObject("Msxml2.DOMDocument.4.0");
    var currNode;
    xmlDoc.async = false;
    xmlDoc.async = false;
    xmlDoc.loadXML("<TABLENAME>      你好你阿三    大法     司法等四              </TABLENAME>");
    currNode = xmlDoc.documentElement;
      
      var s = currNode.xml;
      var r = /\<([^\>\s]*?)[^\>]*?\>([^\<]*?)\<\/\1\>/
      var b = s.replace(r,"$2");
      alert(b);
    //-->
    </SCRIPT>


    //mergeAttributes 复制所有读/写标签属性到指定元素。

    <SCRIPT>
    function fnMerge(){
    oSource.children[1].mergeAttributes(oSource.children[0]);
    }
    </SCRIPT>
    <SPAN ID=oSource>
    <DIV
    ID="oDiv"
    ATTRIBUTE1="true"
    ATTRIBUTE2="true"
    onclick="alert('click');"
    onmouseover="this.style.color='#0000FF';"
    onmouseout="this.style.color='#000000';"
    >
    This is a sample <B>DIV</B> element.
    </DIV>
    <DIV ID="oDiv2">
    This is another sample <B>DIV</B> element.
    </DIV>
    </SPAN>
    <INPUT
    TYPE="button"
    VALUE="Merge Attributes"
    onclick="fnMerge()"
    >


    JavaScript[对象.属性]集锦
    SCRIPT. 标记

    用于包含javascript代码.

    语法

    属性

    LANGUAGE 定义脚本语言

    SRC 定义一个URL用以指定以.JS结尾的文件


    windows对象

    每个HTML文档的顶层对象.

    属性

    frames[] 子桢数组.每个子桢数组按源文档中定义的顺序存放.

    feames.length 子桢个数.

    self 当前窗口.

    parent 父窗口(当前窗口是中一个子窗口).

    top 顶层窗口(是所有可见窗口的父窗口).

    status 浏览器状态窗口上的消息.

    defaultStatus 当status无效时,出现在浏览器状态窗口上的缺省消息.

    name 内部名,为由window.open()方法打开的窗口定义的名字.

    方法

    alert("message") 显示含有给定消息的"javascript. Alert"对话框.

    confirm("message") 显示含有给定消息的"Confirm"对话框(有一个OK按钮和一个Cancel按钮).如果用户单击OK返回true,否则返回false.

    prompt("message") 显示一个"prompt"对话框,要求用户根据显示消息给予相应输入.

    open("URL","name") 打开一个新窗口,给予一个指定的名字.

    close() 关闭当前窗口.


    frame对象

    它是整个浏览器窗口的子窗口,除了status,defaultStatus,name属性外,它拥有window对象的全部属性.

    location对象

    含有当前URL的信息.

    属性

    href 整个URL字符串.

    protocol 含有URL第一部分的字符串,如http:

    host 包含有URL中主机名:端口号部分的字符串.如//www.cenpok.net/server/

    hostname 包含URL中主机名的字符串.如http://www.cenpok.net

    port 包含URL中可能存在的端口号字符串.

    pathname URL中"/"以后的部分.如~list/index.htm

    hash "#"号(CGI参数)之后的字符串.

    search "?"号(CGI参数)之后的字符串.


    document对象

    含有当前文档信息的对象.

    属性

    title 当前文档标题,如果未定义,则包含"Untitled".

    location 文档的全URL.

    lastModified 含有文档最后修改日期.

    referrer 调用者URL,即用户是从哪个URL链接到当前页面的.

    bgColor 背景色(#xxxxxx)

    fgColor 前景文本颜色.

    linkColor 超链接颜色.

    vlinkColor 访问过的超链颜色.

    alinkColor 激活链颜色(鼠标按住未放时).

    forms[] 文档中form对象的数组,按定义次序存储.

    forms.length 文档中的form对象数目.

    links[] 与文档中所有HREF链对应的数组对象,按次序定义存储.

    links.length 文档中HREF链的数目.

    anchors[] 锚(...)数组,按次序定义存储.

    anchors.length 文档中锚的数目.

    方法

    write("string") 将字符串突出给当前窗口.(字符串可以含有HTML标记)

    writeln("string") 与write()类似,在结尾追加回车符,只在预定格式文本中(...或...)生效.

    clear() 清当前窗口.

    close() 关闭当前窗口.


    form对象

    属性

    name

    中的NAME属性的字符串值.

    method 中METHOD属性的类值,"0"="GET" ,"1"="POST" .

    action 中ACTION属性的字符串值.

    target 表格数据提交的目标,与标记中相应属性一致.

    elements[index] elements属性包含form中的各个元素.

    length 表格中的元素个数.

    方法

    submit() 提交表格.

    事件处理器onSubmit() 用户单击一个定义好的按钮提交form时运行的代码.

    text和textarea对象

    属性

    name NAME属性的字符串值.

    value 域内容的字符串值.

    defaultValue 域内容的初始字符串值.

    方法

    focus() 设置对象输入焦点.

    blur() 从对象上移走输入焦点.

    select() 选定对象的输入区域.

    事件处理器

    onFocus 当输入焦点进入时执行.

    onBlur 当域失去焦点时执行.

    onSelect 当域中有部分文本被选定时执行.

    onChange 当域失去焦点且域值相对于onFocus执行有所改变时执行.

    复选框(checkbox)对象

    属性
    name NAME属性的字符串值.

    value 复选框内容的字符串值.如果设置了,则为"on",否则为"off".

    checked 复选框内容的布尔值.如果设置了,则为true,否则为false .

    defaultChecked 反映(CHECKED)属性的布尔值(缺省状态).

    方法

    click() 选定复选框,并使之状态为"on".

    事件处理器

    onClick 当用户单击Checkbox时执行.


    单选按钮(radio)对象

    属性

    name NAME属性的字符串值.

    length radio对象中单选按钮的个数.

    value VALUE属性的字符串值.

    checked 布尔值,按下为true,否则为false .

    defaultChecked 反映CHECKED属性值的布尔值.

    方法

    click() 选定单选按钮.

    事件处理器

    onClick 当单选按钮被选定时执行.


    select对象

    属性

    length select对象中对象的个数.

    name 由NAME=属性定义的select对象的内部名.

    selectedIndex select对象中当前被选option的下标.

    options 该属性对应于在HTML中定义select对象时标记中的内容,它有如下属性:

    text 标记后的文本串.

    value VALUE属性的值,当Submit按钮被按下时,该值被提交.

    defaultSelected 反映标记的SELECTED属性的布尔值.

    selected 反映option的当前选择状态的布尔值.

    事件处理器

    onFocus 当输入焦点进入域时执行.

    onBlur 当域失去输入焦点时执行.

    onChange 当域失去焦点且如果域的值相对于onFocus执行时有所改变,则执行onChange.


    Button对象

    表格中有三种类型按钮,由标记中的TYPE属性定义:

    .submit (type="SUBMIT")
    .reset (type="RESET")
    .custom (type="BUTTON")

    所有按钮对象都有如下成分:
    属性

    value VALUE属性的字符串值.

    name NAME属性的字符串值.
    方法

    click() 选定按钮

    事件处理器

    onClick 当按钮被单击时执行.


    submit和reset对象

    属性

    value VALUE=属性的内容.

    name NAME=属性的内容.

    方法

    click() 选定按钮

    事件处理器

    onClick 当按钮被单击时执行.


    password对象

    属性

    defaultValue VALUE=属性的内容.

    name NAME=属性的内容.

    value 目前输入password域的数据.

    方法

    focus() 将焦点带入password域.

    blur 将焦点从password域移出.

    select() 选定password域中的当前数据,以备修改.


    navigator对象
    该对象用于确定用户访问时使用的Navigator版本.

    属性

    appCodeName 相对于用户浏览器的"codename"

    appName 相对于用户浏览器的实际名字.

    appVersion 相对于用户浏览器的版本号.

    userAgent 该属性反映用户浏览器的全部信息.


    string对象

    string对象为操作字符串的内容提供了很多方法.

    属性

    length 字符串的长度,即字符串中字符的个数.

    方法

    big(),blink(),bold(),fixed(),italics(),small(),sub(),strike(),sup(),fontColor(color),fontSize(size)

    以上方法为字符串增加相应的HTML标记.

    charAt(index) 返回字符串中index处的字符.

    indexOf(searchValue,[fromIndex]) 该方法在字符串中寻找第一次出现的searchValue.如果给定了fromIndex,则从 字符串内该位置开始搜索,当searchValue找到后,返回该串第一个字符的位置.

    lastIndexOf(searchValue,[fromIndex]) 从字符串的尾部向前搜索searchValue,并报告找到的第一个实例.

    substring(indexA,indexB) 获取自indexA到indexB的子串.

    toLowerCase(),toUpperCase() 将字符串中所有字符全部转换成大写,小写.


    Date对象

    要使用Date对象,必须先生成一个Date实例:

    变量名=new Date();

    方法
    getDay(),getDate(),getHours(),getMinutes(),getMonth(),getSeconds(),getTime(),
    getTimeZoneOffset(),getYear()

    还有setDay... ...

    toGMTString() 用GMT格式返回当前时间. (Sun,12 Feb 1999 14:19:22 GMT)

    toLocaleString 用locale格式返回当前时间. (03/11/99 14:19:22)

    parse(date) 将普通date字符串转换成豪秒形式,从而给setTime()做参数.


    Math对象

    属性

    LN10 (10的自然对数)

    PI (3.1415926...)

    SQRT1_2 (1/2的平方根)

    方法

    abs(x) 返回x的绝对值
    acos(x) 返回x的arc cosine值
    asin(x) 返回x的arc sin值
    atan(x) 返回x的arc tangent值
    ceil(x) 返回大于等于x的最小整数
    cos(x) 返回x的cosine值
    exp(x) 返回e的x次方
    floor(x) 返回小于等于x的最大整数
    log(x) 返回x的
    max(x,y) 返回x,y中的大值
    min(x,y) 返回x,y中的小值
    pow(x,y) 返回x的y次方
    round(x) 舍入到最近整数,(小于或等于0.5小数舍去)
    sin(x) 返回x的sin值
    sqrt(x) 返回x的平方根
    tan(x) 返回x的tangent值


    弹窗代码汇总
    【0、超完美弹窗代码 】
    功能:5小时弹一次+背后弹出+自动适应不同分辩率+准全屏显示

    代码:
    <script>
    function openwin(){
    window.open(http://www.6882.com,"pop1","width="+(window.screen.width-15)+",height="+(window.screen.height-170)+",left=0,top=0,toolbar=yes,menubar=yes,scrollbars=yes,resizable=yes,location=yes,status=yes")
    setTimeout("focus();",5);
    }
    function get_cookie(Name) {
    var search = Name + "="
    var return&#118alue = "";
    if (documents&#46cookie.length > 0) {
    offset = documents&#46cookie.indexOf(search)
    if (offset != -1) {
    offset += search.length
    end = documents&#46cookie.indexOf(";", offset);
    if (end == -1)
    end = documents&#46cookie.length;
    return&#118alue=unescape(documents&#46cookie.substring(offset, end))
    }
    }
    return return&#118alue;
    }
    function Set()
    {
      var Then = new Date()    
      Then.setTime(Then.getTime() + 5*60*60*1000 )
      documents&#46cookie = "popped1=yes;expires="+ Then.toGMTString()
    }

    function loadpopup(){
    if (get_cookie('popped1')=='')
    {
    openwin()
    Set()
    }
    }
    setTimeout("loadpopup()",5);

    </script>


    【1、最基本的弹出窗口代码】

    其实代码非常简单:

    <script. language="&#106avascript">
    <!--
    window.open ('page.html')
    -->
    </script>
    因为着是一段&#106avascripts代码,所以它们应该放在<script. language="&#106avascript">标签和</script>之间。<!-- 和 -->是对一些版本低的浏览器起作用,在这些老浏览器中不会将标签中的代码作为文本显示出来。要养成这个好习惯啊。
    window.open ('page.html') 用于控制弹出新的窗口page.html,如果page.html不与主窗口在同一路径下,前面应写明路径,绝对路径(http://)和相对路径(../)均可。用单引号和双引号都可以,只是不要混用。
    这一段代码可以加入html的任意位置,<head>和</head>之间可以,<body>间</body>也可以,越前越早执行,尤其是页面代码长,又想使页面早点弹出就尽量往前放。


    【2、经过设置后的弹出窗口】

    下面再说一说弹出窗口的设置。只要再往上面的代码中加一点东西就可以了。
    我们来定制这个弹出的窗口的外观,尺寸大小,弹出的位置以适应该页面的具体情况。
    <script. language="&#106avascript">
    <!--
    window.open ('page.html', 'newwindow', 'height=100, width=400, top=0,left=0, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=no, status=no')
    //写成一行
    -->
    </script>
    参数解释:
    <script. language="&#106avascript"> js脚本开始;
    window.open 弹出新窗口的命令;
    'page.html' 弹出窗口的文件名;
    'newwindow' 弹出窗口的名字(不是文件名),非必须,可用空''代替;
    height=100 窗口高度;
    width=400 窗口宽度;
    top=0 窗口距离屏幕上方的象素值;
    left=0 窗口距离屏幕左侧的象素值;
    toolbar=no 是否显示工具栏,yes为显示;
    menubar,scrollbars 表示菜单栏和滚动栏。
    resizable=no 是否允许改变窗口大小,yes为允许;
    location=no 是否显示地址栏,yes为允许;
    status=no 是否显示状态栏内的信息(通常是文件已经打开),yes为允许;
    </script> js脚本结束


    【3、用函数控制弹出窗口】

    下面是一个完整的代码。
    <html>
    <head>
    <sc

  • extjs grid 基本练习代码

    2011-04-07 18:58:37

    grid.js

    Ext.onReady(function(){
     //数据格式1:二维数组形式的数据:
     var data=[[1,'EasyJWeb','EaseJF','www.baidu.com'],
            [2,'jfox','huihoo','www.jb31.com'],
            [3,'jdon','jdon','www.test.net'],
            [4,'springside','springside','tool.jb51.net']
       ] ;
     var store= new Ext.data.SimpleStore({data:data,fields:['id','name','organization','homepage']}) ;
     
     //数据格式2:一维数组,数组中的每一个元素是一个对象 --->  json串形式:
    // var data=[{id:1,
    //    name:'EasyJWeb',
    //    organization:'EasyJF',
    //    homepage:'www.baidu.com'},
    //   {id:2,
    //    name:'jfox',
    //    organization:'huihoo',
    //    homepage:'www.jb51.net'},
    //   {id:3,
    //    name:'jdon',
    //    organization:'jdon',
    //    homepage:'s.jb51.net'},
    //   {id:4,
    //    name:'springside',
    //    organization: 'springside',
    //    homepage:'tools.jb51.net'}
    //   ];
    //   var store=new Ext.data.JsonStore({data:data,fields:["id","name","organization","homepage"]});

        //数据格式3:xml格式的数据  --- xml文件为  grid1.xml
    //    var store=new Ext.data.Store({
    //  url:"hello.xml",    // 指定xml文件名
    //  reader:new Ext.data.XmlReader({
    //  record:"row"},      // 指定取数据的标签
    //  ["id","name","organization","homepage"])     // 指定标签,取值
    //  });

      //ColumnModel grid的列配置模板,替代columns配置项
     var cm = new Ext.grid.ColumnModel([{header:'项目名称',dataIndex:'name',sortable:true},
         {header:'开发团队',dataIndex:'organization',sortable:true},
         {header:'网址',dataIndex:'homepage'}]) ;
        
     //  GridPanel 创建表格  
     var grid = new Ext.grid.GridPanel({
      renderTo:'grid1',
      title:'中国java开源产品团队',
      height:150,
      width:600,
    //  columns:[{header:'项目名称',dataIndex:'name',sortable:true},
    //     {header:'开发团队',dataIndex:'organization'},
    //     {header:'网址',dataIndex:'homepage'}],
      // columns(列的定义数组)配置项被替换为cm(ColumnModel)配置项,来配置列的定义
      cm:cm,
      store:store,
      autoExpandColumn:2
     
     });
     
     
     
     
     //store.load();  是用来加载数据

     
     
     
     
    });

     

     

    <body> <div id="grid1"></div>  </body>

     

    D:\myeclipseworkspace\extjsTest\WebRoot\example\grid1.html

    参考文档:

    http://www.jb51.net/article/23625.htm

    参考资料附件: grid.rar(629 KB)

     

     

  • java中产生随机汉字(推荐)

    2011-04-01 18:53:32

     

    4e00-9fa5编码范围,这个范围里应该都是中文。。。

     

    引用楼主 hyb2008dxy 的帖子:
    java中如何产生一个随机的汉字,不用预存在数组的方式来做的话,有没有其他方法?

    答:参考代码如下:

    Java code
    //在0x4e00---0x9fa5之间产生一个随机的字符 public static char getRandomChar() { return (char)(0x4e00+(int)(Math.random()*(0x9fa5-0x4e00+1))); }

     

     

     

    方法二:

    给LZ写了一个,不晓得要得不?
    import java.util.Date;
    import java.util.Random;

    public class Chinese {
    public String getChinese(long seed) throws Exception {
    String str = null;
    int highPos, lowPos;
    seed = new Date().getTime();
    Random random = new Random(seed);
    highPos = (176 + Math.abs(random.nextInt(39)));
    lowPos = 161 + Math.abs(random.nextInt(93));
    byte[] b = new byte[2];
    b[0] = (new Integer(highPos)).byteValue();
    b[1] = (new Integer(lowPos)).byteValue();
    str = new String(b, "GB2312");
    return str;
    }

    public static String get300Chinese() throws Exception {
    Chinese ch = new Chinese();
    String str = "";
    for (int i = 300; i > 0; i--) {
    str = str + ch.getChinese(i);

    }
    System.out.println(str);
    return str;
    }

    public static void main(String[] args) throws Exception {

    get300Chinese();
    }
    }

     

    方法三:

    Java code
    public class Test1 { public static void main(String[] args) { RandomHan han = new RandomHan(); System.out.println(han.getRandomHan()); } } class RandomHan { private Random ran = new Random(); private final static int delta = 0x9fa5 - 0x4e00 + 1; public char getRandomHan() { return (char)(0x4e00 + ran.nextInt(delta)); } }

     

    方法四:

    import java.util.Date;
    import java.util.Random;

    public class Chinese {
    public String getChinese(long seed) throws Exception {
    String str = null;
    int highPos, lowPos;
    seed = new Date().getTime();
    Random random = new Random(seed);
    highPos = (176 + Math.abs(random.nextInt(39)));
    lowPos = 161 + Math.abs(random.nextInt(93));
    byte[] b = new byte[2];
    b[0] = (new Integer(highPos)).byteValue();
    b[1] = (new Integer(lowPos)).byteValue();
    str = new String(b, "GB2312");
    return str;
    }

    public static String get300Chinese() throws Exception {
    Chinese ch = new Chinese();
    String str = "";
    for (int i = 300; i > 0; i--) {
    str = str + ch.getChinese(i);

    }
    System.out.println(str);
    return str;
    }

    public static void main(String[] args) throws Exception {

    get300Chinese();
    }
    }

     

     

     

     

     

    转自: http://topic.csdn.net/u/20080507/14/09d90a22-7f46-44ba-9484-d5ca3d3093a4.html

     

    java 随机字母输出到txt文档
    2007年03月15日 星期四 16:36

    import java.io.FileWriter;

    public class create {

    public static void main(String[] args) {
       char s;
       char k[] = new char[1000];
       double m;
       try{
        for(int i=0;i<1000;i++){
         m = Math.random()*26+97;
         s = (char)m;
         System.out.print(s);
         k[i] = s;
        }
        FileWriter FW = new FileWriter("c:/123.txt");
        FW.write(k);
        FW.close();
       }
       catch(Exception e){
       }
    }
    }

     

     

     

     

     

     

     

     

     

     

     

881/512345>
Open Toolbar