浅析JAVA之垃圾回收机制_第1页
浅析JAVA之垃圾回收机制_第2页
浅析JAVA之垃圾回收机制_第3页
浅析JAVA之垃圾回收机制_第4页
浅析JAVA之垃圾回收机制_第5页
已阅读5页,还剩27页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、浅 HYPERLINK /xieyuooo/article/details/5617007 析JAVA之垃圾回收机制 分类: HYPERLINK /xieyuooo/article/category/669875 JAVA应用开发 HYPERLINK /xieyuooo/article/category/840462 JVM 2010-05-22 22:03 401人阅读 HYPERLINK /xieyuooo/article/details/5617007 l comments 评论(2) HYPERLINK javascript:void(0); o 收藏 收藏 HYPERLINK /xi

2、eyuooo/article/details/5617007 l report o 举报 举报 对于JAVA编程和很多类似C、C+语言有一个巨大区别就是内存不需要自己去free或者delete,而是由JVM垃圾回收机制去完成的。对于这个过程很多人一直比较茫然或者觉得很智能,使得在写程序的过程不太考虑它的感受,其实知道一些内在的原理,帮助我们编写更加优秀的代码是非常有必要的;本文介绍一些JVM垃圾回收的基本知识,后续的文章中会深入探讨JVM的内在;首先在看文章之前大家需要知道为什么要写JVM垃圾回收,在Java发展以来,由于需要面向对象,而屏蔽掉程序员对于底层的关心,所以在性能上存在很多的缺陷,

3、而通过不断改良,很多缺陷已经逐渐的取消掉了,不过还是依然存在很多的问题,其中最大的一块问题就是JVM的垃圾回收机制,一直以来Java在设计实时系统上都被骂声重重,就是因为垃圾回收存在非常多的问题,世界上目前还没有任何一个垃圾回收机制可以做到无暂停,而只是某些系统可以做到非常少的暂停;本文还不会讨论那么深入的只是,就简单的内部认识做一些概要性的介绍。本文从以下几个方面进行阐述:1、finalize()方法2、System.gc()方法及一些实用方法3、JAVA如何申请内存,和C、C+有何区别4、JVM如何寻找到需要回收的内存5、JVM如何回收内存的(回收算法分解详述)6、应用服务器部署及常用参数

4、设置7、扩展话题JIT(即时编译技术)与lazy evaluation(惰性评估),如何在应用服务器中控制一些必要的信息(小小代码参考)1、finalize()方法: 为了说明JVM回收,不得不先说明一个问题就是关于finalize()方法,所有实体对象都会有这个方法,因为这个Object类定义的,这个可能会被认为是垃圾回收的方法或者叫做析构函数,其实并非如此。finalize在JVM内存会收前会被调用(单并非绝对),而即使不调用它,JVM回收机制通过后面所述的一些算法就可以定位哪些是垃圾内存,那么这个拿来干什么用呢?finalize()其实是要做一些特殊的内存回收操作,如果对JAVA研究稍微

5、多一点,大家会发现JAVA中有一种JNI(Java native interface),这种属于JAVA本地接口调用,即调用本地的其他语言信息,JAVA虚拟机底层掉调用也是这样实现的,这部分调用中可能存在一些对C、C+语言的操作,在C和C+内部通过new、malloc、realloc等关键词创建的对象垃圾回收机制是无能为力的,因为这不是它要管理的范围,而平时这些对象可能被JAVA对应的实体所调用,那么需要在对应JAVA对象放弃时(并不代表回收,只是程序中不使用它了)去调用对应的C、C+提供的本地接口去释放这段内存信息,他们的释放同样需要通过free或delete去释放,所以我们一般情况下不要滥

6、用finalize(),个人建议是最好不要用,所有非同类语言的调用不一定非要通过JNI来完成的,或者调用完就直接释放掉相应的内容,而不要寄希望于finalize这个方法,因为JVM不保证什么时候会调用这个方法。2、System.gc()或者Runtime.getRuntime().gc();这个可以被认为是强制垃圾回收的一种机制,但是并非强制回收,只是向JVM建议可以进行垃圾回收,而且垃圾回收的地方和多少是不能像C语言一样控制,这是JVM垃圾回收机去控制的。程序中尽量不要是去使用这些东西,除自己开发一些管理代码除外,一般由JVM自己管理即可。这里顺便提及几个查看当前JVM内存的几个简单代码方法

7、(在JVM监控下有很多的工具,而且不同的厂商也有自己不同的工具,不过后续大部分关于java的文章都是只提及到:Hotspot VM的版本,其他的版本可能只是略微说明下):2.1.设置的最大内存:-Xmx等值:(Runtime.getRuntime().maxMemory()/ (1024 * 1024) + MB2.2.当前JVM可使用的内存,这个值初始化和-Xms等值,若加载东西超过这个值,那么以下值会跟着变大,不过上限为-Xmx,由于变动过程中需要将虚拟内存做不断的伸缩过程,所以我们推荐服务器:是-Xms等价于-Xmx的值:(Runtime.getRuntime().totalMemory

8、()/ (1024 * 1024) + MB2.3.剩余内存,在当前可使用内存基础上,剩余内存等价于其剪掉使用了的内存容量:(Runtime.getRuntime().freeMemory()/ (1024 * 1024) + MB同理如果要查看使用了多少内存或者百分比。可以通过上述几个参数进行运算查看到。顺便在这里提供几个实用方法和类,这部分可能和JVM回收关系不大,不过只是相关推敲,扩展知识面,而且也较为实用的东西:2.4.获取JAVA中的所有系统级属性值(包含虚拟机版本、操作系统、字符集等等信息):System.setProperty(AAA, 123445);Properties pr

9、operties = System.getProperties();Enumeration e = properties.keys();while (e.hasMoreElements() String key = (String) e.nextElement();System.out.println(key + = + properties.getProperty(key);2.5.获取系统中所有的环境变量信息:Map env = System.getenv();for (Iterator iterator = env.keySet().iterator(); iterator.hasNex

10、t();) String key = iterator.next();System.out.println(key + = + env.get(key);System.out.println(System.getenv(CLASSPATH);2.6.在Win环境下,打开一个记事本和一个WORD文档:try Runtime.getRuntime().exec(notepad); Runtime.getRuntime().exec(cmd /c start Winword);catch(Exception e) e.printStackTrace();2.7.查询当前SERVER下所有的线程信息列

11、表情况(这里需要提供两个步骤,首先要根据任意一个线程获取到顶级线程组的句柄(有关线程的说明,后面专门会有一篇文章说明),然后通过顶级线程组得到其存在线程信息,进行一份拷贝,给与遍历):2.7.1.这里通过当前线程得到顶级线程组信息:public static ThreadGroup getHeadThreadGroup() Thread t = Thread.currentThread();ThreadGroup group = t.getThreadGroup();while(group.getParent() != null) group = group.getParent();retur

12、n group;2.7.2.通过得到的顶级线程组,遍历存在的子元素信息(仅仅遍历常用属性):public static void disAllThread(ThreadGroup threadgroup) Thread list = new Threadthreadgroup.activeCount();threadgroup.enumerate(list);for(Thread thread:list) System.out.println(thread.getId()+/t+thread.getName()+/t+thread.getThreadGroup()+/t +thread.get

13、State()+/t+thread.isAlive();2.7.3.测试方法如:类名.disAllThread(getHeadThreadGroup();即可完成,第一个方法带有不断向上查询的过程,这个过程可能在一般情况下也不会太慢,不过我们最好将其记录在一个地方,方便我们提供管理类来进行直接管理,而不需要每次去获取,对外调用都是封装的运行过程而已。好,回到话题,继续说明JVM垃圾回收机制的信息,下面开始说明JAVA申请内存、回收内存的机制了。3、JAVA如何申请内存,和C、C+有何区别。在上一次缩写的关于JAVA集合类文章中其实已经有部分说明,可以大致看到JAVA内部是按照句柄指向实体的过程

14、,不过这是从JAVA程序设计的角度去理解,如果我们需要更加细致的问一个问题是:JVM垃圾回收机制是如何知道哪些内存是垃圾内存的?JVM为什么不在平时就去回收内存,而是要等到内存不够用的时候才会去回收内存?不得不让我进一步去探讨JAVA是如何细节的申请内存的。从编程思想的角度来说,C、C+new申请的内存也是通过指针指向完成,不过你可以看成是一个地球板块图,在这些板块中,他们去new的过程中,就是好比是找一个版块,因为C、C+在申请内存的过程中,是不断的free和delete操作,所以会产生很多内存的碎片操作,而JAVA不是,JAVA只有内存不够用的时候才会去回收(回收细节讲会在文章后面介绍),

15、也就是说,可以保证内存在一定程度上是连续的。从某种意义上将,只要下一块申请的内存不会到头,就可以继续在上一块申请内存的后面紧跟着去申请内存,那么从某种意义上讲,其申请的开销可能可以和C+媲美。那么JAVA在回收内存后,内存还能是连续的嘛。我们姑且这样去理解,在第五节会说明。继续深入话题:在启动weblogic的时候,如果打开任务管理器,可以马上发现,内存被占用了最少-Xms的大小,一个说明现象就是JVM首先将内存先占用了,然后再分配给其对象的,也就是说我们所谓的new可以理解为在堆上做了一个标记,所以在一定程度上做连续分配内存是可以实现的,只是你会发现若要真正实现连续,必然导致一定程度上的序列

16、化,所以new的开销一般还是蛮大的,即使在后面说的JVM会将内存分成几个大块来完成操作,但是也避免不了序列化的过程。在这里一个小推敲就是,一个SERVER的管理内存范围一般不要太大(一般在12G一个SERVER),推荐也不要太大,因数去考虑:1、JAVA虚拟机回收内存是在不够用的时候再去回收,这个不够用何以说明,很多时候因为计算上的失误导致内存溢出。2、如果一个主机只有2G左右内存,很少的CPU,那么一个JVM也好,但是如果主机很好,如32G内存,那么这样做未必有点过,第一发挥不出来,一个JVM管这么大块内存好像有点过,还有内存不够用去回收这么大块内存(回收内存时一般需暂停服务),需要花时间,

17、第二举个很现实的例子,一个学校如果只有2030人,一个人可以既当校长又当老师,如果一个学校有几百上千人,我想这个人再大的能力忙死也管不过来,而且会出乱子,此时它要请班主任来管了。3、对于大内存来说,使用多个SERVER完成负载均衡,一个暂停服务回收内存,另一个还可以运行嘛。但是JVM是不是真的就不支持大内存了呢?现在你可以这样理解,因为到目前为止可以这样认为,因为世界上所有的java虚拟机,没有不暂停的,而内存越大,回收的时间是必然越长的,不论有多么优秀的算法还做不到“不暂停”的这一点,所以我们的目标是尽量少的暂停,现在的CMS GC已经让我们看到了希望,不过还存在很多的缺陷,我们期待G1的成

18、熟版本的出现,G1的论文很清晰,不过现在还没有一个成熟的版本,所以很期待。4、JVM如何寻找到需要回收的内存:要回收垃圾,那么首先要知道哪些内存是垃圾,或者反过来哪些不是垃圾,这个过程我们一般称为:Mark的过程,Mark过程世界上没有任何一门虚拟机不进行对外暂停。4.1、 引用计数算法:引用计数这里简单说明下,就是当一个引用被赋值的时候,虚拟机将会被知道(部分虚拟机通过写屏障实现),多一个引用,对象的计数增加1,少一个减少1,回收时,只回收等于0的,好处是算法非常简单,而且这种算法由于回收过程中只是看那些没有被引用,所以在一般情况下无需暂停,不过由于它在计数的过程中需要一个锁的机制,而且遍历

19、内存的过程十分漫长,所以现在已经没有这个东西的存在了;另外一个问题出来了:问题出来了:循环引用,以及被这些对象引用的对象都讲永远回收不掉,因为循环引用中的对象引用计数永远大于等于1,那么这个资源在循环引用中,其实不是虚拟机算不出来,而且为了这个非常低的代价,虚拟机的算法将会复杂非常多。其次这种分配方法在分配回收的过程中因为需要记录哪些内存是垃圾,哪些不是垃圾,所以一般需要维护一个freelist的区域。4.2.引用树遍历算法:首先,每个内存都有原始的引用根,这些根部一般来源于当前线程的栈针、静态引用、JNI的句柄等,从这里开始mark,将可达的对象标记为活着的对象,其余的就认为不是活着的对象,

20、至于找到这些对象如何处理也就是回收的算法所决定的。5、JVM如何回收内存的(回收算法分解详述):首先了解几个其他的概念:5.1平时所说的JDK,其实是JAVA开发工具的意思,安装JAVA虚拟机会产生两个JRE目录,JRE目录为JAVA运行时环境的意思,两个JRE目录的区别是其中在JDK所在的JRE目录下没有Server和Client文件夹(JDK1.5自动安装包会自动将其复制到JDK下面一份),JRE为运行时环境,提供对JVM操作的API,JVM内部通过动态链接库(就是配置PATH的路径下),通过它作为主动态链接库寻找到其它的动态链接库,动态链接库为何OS绑定的参数,即代码最终要通过这些东西转

21、换为操作系统指令集进行运行,另一个核心工具为JIT(JAVA即时编译工具),用于将代码转换为对应操作系统的运行指令集合的过程,不过其与惰性评估形成对比,后面会专门介绍。5.1.JVM首先将大致分为:JVM指令集、JVM存储器、JVM内存(堆栈区域部分)、JVM垃圾回收区域;JVM的堆部分又一般分为:新域、旧域、永久域(很多时候不会认为永久域是堆的一部分,因为它是永远不会被回收的,它一般包含class的定义信息、static定义的方法、static匿名块代码段、常量信息(较为典型的就是String常量),不过这块内存也是可以被配置的);新域内部又可以分为Eden和两个救助区域,这几个对象在JVM

22、内部有一定的默认值,但是也是可以被设置的。当新申请的对象的时候,会放入Eden区中(这个区域一般不会太大,默认为新域的3/4, 还有1/4一般会被切成两块,成为救助域),当对象在一定时间内还在使用的时候,它会逐步的进入旧域(此时是一个内存复制的过程,旧区域按照顺序,其引用的句柄也会被修改指向的位置),JVM回收中会先将Eden里面的内存和一个救助区域的内存就会被赋值到另一个救助区域,然后对这两块内存进行回收,同理,旧区域也有一个差不多大小的内存区域进行被复制,这个复制的过程肯定就会在一定程度上将内存连续的排列起来;另外可以想到JAVA提供内存复制最快的就是System.arrayCopy方法,

23、那么这个肯定是按照内存数组进行拷贝(JVM起始就是一个大内存,本身就可以成是几个大数组组成的,而这个拷贝方法,默认拷贝多长呢,其实数组最长可以达到多少,通过数组的length返回的是int类型数据就可以清楚发现,为int类型的上限1java -Xmn4m -Xms16m -Xmx16m Hello第一次申请第二次申请Exception in thread main java.lang.OutOfMemoryError: Java heap space分析下为什么会这样,Heap总大小为16M,而Young的大小为4M,一般情况下的默认值Eden为Young的80%,所以Eden肯定不到4M,其

24、实初始化直接申请4M空间Enden肯定放不下(抛开对象头部本身的区域也有4M),此时直接放入Old区域,Old区域本身自有剩下12M,第二次也是一样,当尝试第三次放入4M时,JVM检查空间已经不够了,并且以前的空间释放不掉,所以就直接抛出异常了,而不是先将内存放下去,这样引起的是类似于其他语言类似的OS级别的错误,导致的问题就是操作系统直接将进城Crash掉。那么我们将程序修改一下再看效果:public class Hello public static void main(String args) byte a1 = new byte4*1024*1024;System.out.printl

25、n(第一次申请);a1 = new byte4*1024*1024;System.out.println(第二次申请);a1 = new byte4*1024*1024;System.out.println(第三次申请);a1 = new byte4*1024*1024;System.out.println(第四次申请);a1 = new byte4*1024*1024;System.out.println(第五次申请);a1 = new byte4*1024*1024;System.out.println(第六次申请);运行程序如下:C:/javac Hello.javaC:/java -X

26、mn4m -Xms16m -Xmx16m Hello第一次申请第二次申请第三次申请第四次申请第五次申请第六次申请程序正常下来了,说明中途进行了垃圾回收的动作,我们想看下垃圾回收的整个过程,如何看,把上面的参数搬下来:E:/java -Xmn4m -Xms16m -Xmx16m -XX:+PrintGCDetails Hello第一次申请第二次申请GC DefNew: 189K-133K(3712K), 0.0014622 secsTenured: 8192K-4229K(12288K), 0.0089967 secs 8381K-4229K(16000K), 0.0110011 secs第三次

27、申请GC DefNew: 0K-0K(3712K), 0.0004749 secsTenured: 8325K-4229K(12288K), 0.0083114 secs 8325K-4229K(16000K), 0.0092936 secs第四次申请GC DefNew: 0K-0K(3712K), 0.0003168 secsTenured: 8325K-4229K(12288K), 0.0081516 secs 8325K-4229K(16000K), 0.0089735 secs第五次申请GC DefNew: 0K-0K(3712K), 0.0003179 secsTenured: 83

28、25K-4229K(12288K), 0.0080368 secs 8325K-4229K(16000K), 0.0088335 secs第六次申请上面可以看到,DefNew一直就没有怎么回收过,其实刚开始看到的189K只是一些引用空间本身内部的一些开销,而Tenured也就是我们说的老年代的每次GC的变法,而括号中的部分代表该区域实际运行中的最大尺寸,后面会给出GC的延迟时间,顺便说明下,这是默认-client情况下是串行回收,当你使用并行回收的时候看到的提示会有所变化,原因是因为他们完全是两套程序控制,所谓DefNew没什么就是它的程序名称叫做这个,Tenured也是这个意思。对于内存回收

29、部分的内容,这里不想说得太深入,只是让大家有一个大致的了解,后续有空专门写几篇文章为大家分享,下面分享一点点雕虫小技。7、扩展话题JIT(即时编译技术)与lazy evaluation(惰性评估),如何在应用服务器中控制一些必要的信息:7.1.JIT为即时编译技术,虚拟机有两种方案:一种是在启动时将对应的class信息编译对应的机器指令集合,但是这样会导致的问题是装在时间很长,另一个是机器指令码比字节码要长很多,装在的时间页面操作非常大,此时JAVA提出惰性评估方案,即启动时对于CLASS的字节码并不翻译,当需要调用其代码段了,再去编译(注意代码段若装载后,实例存在其对应代码段是不会注销的,单

30、例程序的代码段也是单例的)。7.2.如何在应用服务中控制信息:其实通过上述控制已经发现一些控制原理,当内存在某些特殊的情况下就会内存溢出,尤其在进行一些大批量导出数据的情况下,此时可能会同时导出几万条数据,如果在前端去控制只能到处几百天或者几千条可能客户不答应,因为这太少了;假如我们的控制方式是要在1G内存将各类导出内存数据进行分类:业务类别、平均一百行占用内存多少M。进行计算,然后对于一个SERVER下允许同时在线导出多少个线程进行配置化,按照提交的业务类别,在抽象顶层进行控制,若为导出某类业务将其进行校验,若未通过校验,线程wait(),即释放临界资源,进入等待池,当下载完毕一个时,调用管

31、理器进行对应对象的notify操作,并使得计数器减少。大致原理可以基于以下方式(不过实际应用须稍微修改下):/代码段1:设置共享信息,该类class WaitObj /该类所在对象须申明为单例,才可以达到效果。private volatile int index = 0;private int maxMutile = 20;/假如最多运行20个同时导出synchronized public void checkInfo() /while(index = maxMutile) try this.wait();/超过数量等待激活,激活后还要判定 catch (InterruptedExceptio

32、n e) e.printStackTrace();index+;/得到申请可以导出时,将在线计数器增加1synchronized public void notifyInfo() /做完事情,激活一个index-;this.notify();public void setMaxMutile(String maxMutilePara) /手工设置最大值maxMutile = (maxMutilePara = null)? 20 : Integer.valueOf(maxMutilePara).intValue();/同文件中代码段2:设置管理器,设置控制简单单例,并提供管理规则public cl

33、ass TestManager private final static waitObj = new waitObj();/只有一个实例 public static void checkInfo() waitObj.checkInfo();public static void notifyInfo() waitObj.notifyInfo();/外部代码段调用:假如导出部分代码上层有公共调用部分去调用导出代码,那么在公共代码部分这样写:TestManager.checkInfo();/这里调用了检测部分try export调用部分。根据实际情况而定cache(Exception e) 异常处理

34、finally TestManager.notifyInfo();/执行完毕后,释放一个资源为了验证程序的正确性,可以从几个角度去测试:1、 在本地模拟一个多线程,利用多个线程同时去访问一段代码,这段嗲吗如上,在执行前通过TestManager.checkInfo()序列化操作,在finally中去TestManager.notifyInfo()操作。2、 多线程取一个名字,然后输出名字即可,这里就不提供模拟程序了(因为怕误认为下面的程序为实际的运行程序,下面只是为了模拟情况而已),提供了001007之间七个线程去访问,而最大同时在线导出人数为5个,以打印信息表示动作已经执行,此时运行结果如下

35、:001执行了,时间:1274763253343002执行了,时间:1274763253359004执行了,时间:1274763253359003执行了,时间:1274763253359006执行了,时间:1274763253359007执行了,时间:1274763253359005执行了,时间:12747632533593、 此时发现,几乎同时执行,为什么,因为程序运行太快,前面执行完后,就直接释放掉信息了,所以看不出什么区别,为了验证先执行完的程序暂时不释放,我们让每个线程执行完(输出信息后)以后,等待两秒再去执行,那么输出结果如下所示:001执行了,时间:1274763842140002

36、执行了,时间:1274763842140004执行了,时间:1274763842140003执行了,时间:1274763842140005执行了,时间:1274763842140007执行了,时间:1274763844140006执行了,时间:1274763844140你会发现,后面两个线程几乎同时执行,前面的线程几乎同时执行,是不是后面两个线程同时运行的呢?这样会不会有问题,如现在堵塞了10个线程在这里,那么一旦释放,那么计数器的值是否会错误,此时的确前面5个线程同时几乎同时释放了(虽然都睡了两秒),为了验证后面两个线程对于计数器的操作是否为顺序的或者互斥的,一种首先在自定义线程中,定义一个

37、自定义的time,初始化的时候,设置不同的值,让他们睡不同的时间来激活。另一个模拟就是在每一个线程进行checkInfo()内部,跳出循环的时候,也睡两秒,此时若程序剩下两个线程能够以相差2秒左右的时间下来就是理想结果,如果是同时下来,那么多线程在这里释放的过程中就没有控制到,代码稍微修改下(测试代码):boolean i = false;while(index = maxMutile) try i = true;this.wait(); catch (InterruptedException e) e.printStackTrace();if(i) try Thread.sleep(2000

38、); catch (InterruptedException e) e.printStackTrace(); index +;此时运行结果如下:001执行了,时间:1274764270203003执行了,时间:1274764270203005执行了,时间:1274764270203002执行了,时间:1274764270203007执行了,时间:1274764270203004执行了,时间:1274764274203006执行了,时间:1274764276203注意:可能你会发现,上面的顺序怎么在变化,因为它是多线程,谁先获取到锁这是随即的,虽然也有顺序,不过在很短的时间内,不一定谁先获取到锁

39、。另外上述为共享锁机制,一般不允许外部代码所直接调用,可以将其作为管理器内部的private static class 的内部类,只有其管理类才可以创建它的实例并直接操作它。这的确是我们要要的结果,那么在一定程度决定了它的正确性,稳定性由程序完成控制过程,并通过管理器控制外部调用,若可以的话,可以将这部分代码进行AOP切入方式控制到程序中,对于AOP切入方式的原理和核心说明,后面再介绍反射中给予详细例子和说明。这种应该说在绝大部分概率下问题不大,因为finally是SUN公司承诺无论发生任何事情,是肯定会执行,唯一可能出现漏洞就是TestManager.notifyInfo()出现了异常,不过

40、这个概率非常低。同理如果要完善自己的一些内存数据的管理,进一步分类管理:1、信息类别:页面流、文章、图片等2、业务分类3、初始化内存行数4、最大数据行数5、置换算法(这个可以配置也可以写死,置换算法比较经典就是LRU最近最久未使用算法,不过写得不好的话,会很慢,还不如直接从数据库里面读,不过细细读每一行意义,总体把握性能,量化评估算法,问题不会太大。)附录资料:java处理高并发高负载类网站的优化方法java处理高并发高负载类网站中数据库的设计方法(java教程,java处理大量数据,java高负载数据)一:高并发高负载类网站关注点之数据库没错,首先是数据库,这是大多数应用所面临的首个SPOF

41、。尤其是Web2.0的应用,数据库的响应是首先要解决的。一般来说MySQL是最常用的,可能最初是一个mysql主机,当数据增加到100万以上,那么,MySQL的效能急剧下降。常用的优化措施是M-S(主-从)方式进行同步复制,将查询和操作和分别在不同的服务器上进行操作。我推荐的是M-M-Slaves方式,2个主Mysql,多个Slaves,需要注意的是,虽然有2个Master,但是同时只有1个是Active,我们可以在一定时候切换。之所以用2个M,是保证M不会又成为系统的SPOF。Slaves可以进一步负载均衡,可以结合LVS,从而将select操作适当的平衡到不同的slaves上。以上架构可以

42、抗衡到一定量的负载,但是随着用户进一步增加,你的用户表数据超过1千万,这时那个M变成了SPOF。你不能任意扩充Slaves,否则复制同步的开销将直线上升,怎么办?我的方法是表分区,从业务层面上进行分区。最简单的,以用户数据为例。根据一定的切分方式,比如id,切分到不同的数据库集群去。全局数据库用于meta数据的查询。缺点是每次查询,会增加一次,比如你要查一个用户nightsailer,你首先要到全局数据库群找到nightsailer对应的cluster id,然后再到指定的cluster找到nightsailer的实际数据。每个cluster可以用m-m方式,或者m-m-slaves方式。这是

43、一个可以扩展的结构,随着负载的增加,你可以简单的增加新的mysql cluster进去。需要注意的是:1、禁用全部auto_increment的字段2、id需要采用通用的算法集中分配3、要具有比较好的方法来监控mysql主机的负载和服务的运行状态。如果你有30台以上的mysql数据库在跑就明白我的意思了。4、不要使用持久性链接(不要用pconnect),相反,使用sqlrelay这种第三方的数据库链接池,或者干脆自己做,因为php4中mysql的链接池经常出问题。二:高并发高负载网站的系统架构之HTML静态化其实大家都知道,效率最高、消耗最小的就是纯静态化,所以我们尽可能使我们的网站上的页面采

44、用静态页面来实现,这个最简单的方法其实也是 最有效的方法。但是对于大量内容并且频繁更新的网站,我们无法全部手动去挨个实现,于是出现了我们常见的信息发布系统CMS,像我们常访问的各个门户站点 的新闻频道,甚至他们的其他频道,都是通过信息发布系统来管理和实现的,信息发布系统可以实现最简单的信息录入自动生成静态页面,还能具备频道管理、权限 管理、自动抓取等功能,对于一个大型网站来说,拥有一套高效、可管理的CMS是必不可少的。除了门户和信息发布类型的网站,对于交互性要求很高的社区类型网站来说,尽可能的静态化也是提高性能的必要手段,将社区内的帖子、文章进行实时的静态化,有更新的时候再重新静态化也是大量使

45、用的策略,像Mop的大杂烩就是使用了这样的策略,网易社区等也是如此。 同时,html静态化也是某些缓存策略使用的手段,对于系统中频繁使用数据库查询但是内容更新很小的应用,可以考虑使用html静态化来实现,比如论坛 中论坛的公用设置信息,这些信息目前的主流论坛都可以进行后台管理并且存储再数据库中,这些信息其实大量被前台程序调用,但是更新频率很小,可以考虑将这 部分内容进行后台更新的时候进行静态化,这样避免了大量的数据库访问请求高并发。网站HTML静态化解决方案当一个Servlet资源请求到达WEB服务器之后我们会填充指定的JSP页面来响应请求:HTTP请求Web服务器Servlet-业务逻辑处理

46、-访问数据-填充JSP-响应请求HTML静态化之后:HTTP请求Web服务器Servlet-HTML-响应请求静态访求如下Servlet:public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException if(request.getParameter(chapterId) != null) String chapterFileName = bookChapterRead_+request.getParameter(chapterId)+

47、.html; String chapterFilePath = getServletContext().getRealPath(/) + chapterFileName; File chapterFile = new File(chapterFilePath); if(chapterFile.exists()response.sendRedirect(chapterFileName);return;/如果有这个文件就告诉浏览器转向 INovelChapterBiz novelChapterBiz = new NovelChapterBizImpl(); NovelChapter novelCh

48、apter = novelChapterBiz.searchNovelChapterById(Integer.parseInt(request.getParameter(chapterId);/章节信息 int lastPageId = novelChapterBiz.searchLastCHapterId(novelChapter.getNovelId().getId(), novelChapter.getId(); int nextPageId = novelChapterBiz.searchNextChapterId(novelChapter.getNovelId().getId(),

49、novelChapter.getId(); request.setAttribute(novelChapter, novelChapter); request.setAttribute(lastPageId, lastPageId); request.setAttribute(nextPageId, nextPageId); new CreateStaticHTMLPage().createStaticHTMLPage(request, response, getServletContext(), chapterFileName, chapterFilePath, /bookRead.jsp)

50、; 生成HTML静态页面的类:package com.jb.y2t034.thefifth.web.servlet;import java.io.ByteArrayOutputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;import java.io.PrintWriter;import javax.servlet.RequestDispatcher;import javax.servlet.ServletContext;import jav

51、ax.servlet.ServletException;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpServletResponseWrapper;/* 创建HTML静态页面* 功能:创建HTML静态页面* 时间:2009年1011日* 地点:home* author mavk*/public class CreateS

52、taticHTMLPage /* * 生成静态HTML页面的方法 * param request 请求对象 * param response 响应对象 * param servletContext Servlet上下文 * param fileName 文件名称 * param fileFullPath 文件完整路径 * param jspPath 需要生成静态文件的JSP路径(相对即可) * throws IOException * throws ServletException */ public void createStaticHTMLPage(HttpServletRequest r

53、equest, HttpServletResponse response,ServletContext servletContext,String fileName,String fileFullPath,String jspPath) throws ServletException, IOException response.setContentType(text/html;charset=gb2312);/设置HTML结果流编码(即HTML文件编码) RequestDispatcher rd = servletContext.getRequestDispatcher(jspPath);/得

54、到JSP资源 final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();/用于从ServletOutputStream中接收资源 final ServletOutputStream servletOuputStream = new ServletOutputStream()/用于从HttpServletResponse中接收资源 public void write(byte b, int off,int len) byteArrayOutputStream.write(b, off, len)

55、; public void write(int b) byteArrayOutputStream.write(b); ; final PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream);/把转换字节流转换成字符流 HttpServletResponse httpServletResponse = new HttpServletResponseWrapper(response)/用于从response获取结果流资源(重写了两个方法) public ServletOutput

56、Stream getOutputStream() return servletOuputStream; public PrintWriter getWriter() return printWriter; ; rd.include(request, httpServletResponse);/发送结果流 printWriter.flush();/刷新缓冲区,把缓冲区的数据输出 FileOutputStream fileOutputStream = new FileOutputStream(fileFullPath); byteArrayOutputStream.writeTo(fileOutp

57、utStream);/把byteArrayOuputStream中的资源全部写入到fileOuputStream中 fileOutputStream.close();/关闭输出流,并释放相关资源 response.sendRedirect(fileName);/发送指定文件流到客户端 三:高并发高负载类网站关注点之缓存、负载均衡、存储缓存是另一个大问题,我一般用memcached来做缓存集群,一般来说部署10台左右就差不多(10g内存池)。需要注意一点,千万不能用使用swap,最好关闭linux的swap。负载均衡/加速可能上面说缓存的时候,有人第一想的是页面静态化,所谓的静态html,我认为

58、这是常识,不属于要点了。页面的静态化随之带来的是静态服务的负载均衡和加速。我认为Lighttped+Squid是最好的方式了。LVS lighttped=squid(s) =lighttpd上面是我经常用的。注意,我没有用apache,除非特定的需求,否则我不部署apache,因为我一般用php-fastcgi配合lighttpd,性能比apache+mod_php要强很多。squid的使用可以解决文件的同步等等问题,但是需要注意,你要很好的监控缓存的命中率,尽可能的提高的90%以上。squid和lighttped也有很多的话题要讨论,这里不赘述。存储存储也是一个大问题,一种是小文件的存储,比

59、如图片这类。另一种是大文件的存储,比如搜索引擎的索引,一般单文件都超过2g以上。小文件的存储最简单的方法是结合lighttpd来进行分布。或者干脆使用Redhat的GFS,优点是应用透明,缺点是费用较高。我是指你购买盘阵的问题。我的项目中,存储量是2-10Tb,我采用了分布式存储。这里要解决文件的复制和冗余。这样每个文件有不同的冗余,这方面可以参考google的gfs的论文。大文件的存储,可以参考nutch的方案,现在已经独立为hadoop子项目。(你可以google it)其他:此外,passport等也是考虑的,不过都属于比较简单的了。四:高并发高负载网站的系统架构之图片服务器分离大家知道

60、,对于Web 服务器来说,不管是Apache、IIS还是其他容器,图片是最消耗资源的,于是我们有必要将图片与页面进行分离,这是基本上大型网站都会采用的策略,他 们都有独立的图片服务器,甚至很多台图片服务器。这样的架构可以降低提供页面访问请求的服务器系统压力,并且可以保证系统不会因为图片问题而崩溃,在应用 服务器和图片服务器上,可以进行不同的配置优化,比如apache在配置ContentType的时候可以尽量少支持,尽可能少的LoadModule, 保证更高的系统消耗和执行效率。利用Apache实现图片服务器的分离缘由:起步阶段的应用,都可能部署在一台服务器上(费用上的原因)第一个优先分离的,肯

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论