JVM工做原理和特色主要是指操做系统装入JVM是经过jdk中Java.exe来完成,经过下面4步来完成JVM环境.html
1.建立JVM装载环境和配置java
2.装载JVM.dll程序员
3.初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例算法
4.调用JNIEnv实例装载并处理class类。apache
在咱们运行和调试Java程序的时候,常常会提到一个JVM的概念.JVM是Java程序运行的环境,可是他同时一个操做系统的一个应用程序一个进程,所以他也有他本身的运行的生命周期,也有本身的代码和数据空间.windows
首先来讲一下JVM工做原理中的jdk这个东西,无论你是初学者仍是高手,是j2ee程序员仍是j2se程序员,jdk老是在帮咱们作一些事情.我 们在了解Java以前首先大师们会给咱们提供说jdk这个东西.它在Java整个体系中充当着什么角色呢?我很惊叹sun大师们设计天才,能把一个如此完 整的体系结构化的如此完美.jdk在这个体系中充当一个生产加工中心,产生全部的数据输出,是全部指令和战略的执行中心.自己它提供了Java的完整方 案,能够开发目前Java能支持的全部应用和系统程序.这里说一个问题,你们会问,那为何还有j2me,j2ee这些东西,这两个东西目的很简单,分别 用来简化各自领域内的开发和构建过程.jdk除了JVM以外,还有一些核心的API,集成API,用户工具,开发技术,开发工具和API等组成api
好了,废话说了那么多,来点于主题相关的东西吧.JVM在整个jdk中处于最底层,负责于操做系统的交互,用来屏蔽操做系统环境,提供一个完整的 Java运行环境,所以也就虚拟计算机. 操做系统装入JVM是经过jdk中Java.exe来完成,经过下面4步来完成JVM环境.缓存
1.建立JVM装载环境和配置安全
2.装载JVM.dll服务器
3.初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例
4.调用JNIEnv实例装载并处理class类。
一.JVM装入环境,JVM提供的方式是操做系统的动态链接文件.既然是文件那就一个装入路径的问题,Java 是怎么找这个路径的呢?当你在调用Java test的时候,操做系统会在path下在你的Java.exe程序,Java.exe就经过下面一个过程来肯定JVM的路径和相关的参数配置了.下面基 于Windows的实现的分析.
首先查找jre路径,Java是经过GetApplicationHome api来得到当前的Java.exe绝对路径,c:\j2sdk1.4.2_09\bin\Java.exe,那么它会截取到绝对路径c: \j2sdk1.4.2_09\,判断c:\j2sdk1.4.2_09\bin\Java.dll文件是否存在,若是存在就把c: \j2sdk1.4.2_09\做为jre路径,若是不存在则判断c:\j2sdk1.4.2_09\jre\bin\Java.dll是否存在,若是存 在这c:\j2sdk1.4.2_09\jre做为jre路径.若是不存在调用GetPublicJREHome查HKEY_LOCAL_MACHINE \Software\JavaSoft\Java Runtime Environment\“当前JRE版本号”\JavaHome的路径为jre路径。
而后装载JVM.cfg文件JRE路径+\lib+\ARCH(CPU构架)+\JVM.cfgARCH(CPU构架)的判断是经过 Java_md.c中GetArch函数判断的,该函数中windows平台只有两种状况:WIN64的‘ia64’,其余状况都为‘i386’。以个人 为例:C:\j2sdk1.4.2_09\jre\lib\i386\JVM.cfg.主要的内容以下:
在咱们的jdk目录中jre\bin\server和jre\bin\client都有JVM.dll文件存在,而Java正是经过JVM.cfg 配置文件来管理这些不一样版本的JVM.dll的.经过文件咱们能够定义目前jdk中支持那些JVM,前面部分(client)是JVM名称,后面是参 数,KNOWN表示JVM存在,ALIASED_TO表示给别的JVM取一个别名,WARN表示不存在时找一个JVM替代,ERROR表示不存在抛出异 常.在运行Java XXX是,Java.exe会经过CheckJVMType来检查当前的JVM类型,Java能够经过两种参数的方式来指定具体的JVM类型,一种按照 JVM.cfg文件中的JVM名称指定,第二种方法是直接指定,它们执行的方法分别是“Java -J”、“Java -XXaltJVM=”或“Java -J-XXaltJVM=”。若是是第一种参数传递方式,CheckJVMType函数会取参数‘-J’后面的JVM名称,而后从已知的JVM配置参数中 查找若是找到同名的则去掉该JVM名称前的‘-’直接返回该值;而第二种方法,会直接返回“-XXaltJVM=”或“-J-XXaltJVM=”后面的 JVM类型名称;若是在运行Java时未指定上面两种方法中的任一一种参数,CheckJVMType会取配置文件中第一个配置中的JVM名称,去掉名称 前面的‘-’返回该值。CheckJVMType函数的这个返回值会在下面的函数中汇同jre路径组合成JVM.dll的绝对路径。若是没有指定这会使用 JVM.cfg中第一个定义的JVM.能够经过set _Java_LAUNCHER_DEBUG=1在控制台上测试.
最后得到JVM.dll的路径,JRE路径+\bin+\JVM类型字符串+\JVM.dll就是JVM的文件路径了,可是若是在调用Java程序时用-XXaltJVM=参数指定的路径path,就直接用path+\JVM.dll文件作为JVM.dll的文件路径.
二:装载JVM.dll
经过第一步已经找到了JVM的路径,Java经过LoadJavaVM来装入JVM.dll文件.装入工做很简单就是调用Windows API函数:
LoadLibrary装载JVM.dll动态链接库.而后把JVM.dll中的导出函数JNI_CreateJavaVM和 JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和 GetDefaultJavaVMInitArgs函数指针变量上。JVM.dll的装载工做宣告完成。
三:初始化JVM,得到本地调用接口,这样就能够在Java中调用JVM的函数了.调用InvocationFunctions->CreateJavaVM也就是JVM中JNI_CreateJavaVM方法得到JNIEnv结构的实例.
四:运行Java程序.
Java程序有两种方式一种是jar包,一种是class. 运行jar,Java -jar XXX.jar运行的时候,Java.exe调用GetMainClassName函数,该函数先得到JNIEnv实例而后调用Java类 Java.util.jar.JarFileJNIEnv中方法getManifest()并从返回的Manifest对象中取 getAttributes("Main-Class")的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的 主类名做为运行的主类。以后main函数会调用Java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。main 函数直接调用Java.c中LoadClass方法装载该类。若是是执行class方法。main函数直接调用Java.c中LoadClass方法装载 该类。
而后main函数调用JNIEnv实例的GetStaticMethodID方法查找装载的class主类中
“public static void main(String[] args)”方法,并判断该方法是否为public方法,而后调用JNIEnv实例的
CallStaticVoidMethod方法调用该Java类的main方法。
如下转自:http://blog.csdn.net/cnhzgb/article/details/7179419
= GC 基础 =====================
JAVA堆的描述以下:
内存由 Perm 和 Heap 组成. 其中
Heap = {Old + NEW = { Eden , from, to } }
JVM内存模型中分两大块,一块是 NEW Generation, 另外一块是Old Generation. 在New Generation中,有一个叫Eden的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(from,to), 它们用来存放每次垃圾回收后存活下来的对象。在Old Generation中,主要存放应用程序中生命周期长的内存对象,还有个Permanent Generation,主要用来放JVM本身的反射对象,好比类对象和方法对象等。
垃圾回收描述:
在New Generation块中,垃圾回收通常用Copying的算法,速度快。每次GC的时候,存活下来的对象首先由Eden拷贝到某个Survivor Space, 当Survivor Space空间满了后, 剩下的live对象就被直接拷贝到Old Generation中去。所以,每次GC后,Eden内存块会被清空。在Old Generation块中,垃圾回收通常用mark-compact的算法,速度慢些,但减小内存要求.
垃圾回收分多级,0级为所有(Full)的垃圾回收,会回收OLD段中的垃圾;1级或以上为部分垃圾回收,只会回收NEW中的垃圾,内存溢出一般发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的状况。
当一个URL被访问时,内存申请过程以下:
A. JVM会试图为相关Java对象在Eden中初始化一块内存区域
B. 当Eden空间足够时,内存申请结束。不然到下一步
C. JVM试图释放在Eden中全部不活跃的对象(这属于1或更高级的垃圾回收), 释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区
D. Survivor区被用来做为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,不然会被保留在Survivor区
E. 当OLD区空间不够时,JVM会在OLD区进行彻底的垃圾收集(0级)
F. 彻底垃圾收集后,若Survivor及OLD区仍然没法存放从Eden复制过来的部分对象,致使JVM没法在Eden区为新对象建立内存区域,则出现”out of memory错误”
JVM调优建议:
ms/mx:定义YOUNG+OLD段的总尺寸,ms为JVM启动时YOUNG+OLD的内存大小;mx为最大可占用的YOUNG+OLD内存大小。在用户生产环境上通常将这两个值设为相同,以减小运行期间系统在内存申请上所花的开销。
NewSize/MaxNewSize:定义YOUNG段的尺寸,NewSize为JVM启动时YOUNG的内存大小;MaxNewSize为最大可占用的YOUNG内存大小。在用户生产环境上通常将这两个值设为相同,以减小运行期间系统在内存申请上所花的开销。
PermSize/MaxPermSize:定义Perm段的尺寸,PermSize为JVM启动时Perm的内存大小;MaxPermSize为最大可占用的Perm内存大小。在用户生产环境上通常将这两个值设为相同,以减小运行期间系统在内存申请上所花的开销。
SurvivorRatio:设置Survivor空间和Eden空间的比例
内存溢出的可能性
1. OLD段溢出
这种内存溢出是最多见的状况之一,产生的缘由多是:
1) 设置的内存参数太小(ms/mx, NewSize/MaxNewSize)
2) 程序问题
单个程序持续进行消耗内存的处理,如循环几千次的字符串处理,对字符串处理应建议使用StringBuffer。此时不会报内存溢出错,却会使系统持续垃 圾收集,没法处理其它请求,相关问题程序可经过Thread Dump获取(见系统问题诊断一章)单个程序所申请内存过大,有的程序会申请几十乃至几百兆内存,此时JVM也会因没法申请到资源而出现内存溢出,对此首 先要找到相关功能,而后交予程序员修改,要找到相关程序,必须在Apache日志中寻找。
当Java对象使用完毕后,其所引用的对象却没有销毁,使得JVM认为他仍是活跃的对象而不进行回收,这样累计占用了大量内存而没法释放。因为目前市面上尚未对系统影响小的内存分析工具,故此时只能和程序员一块儿定位。
2. Perm段溢出
一般因为Perm段装载了大量的Servlet类而致使溢出,目前的解决办法:
1) 将PermSize扩大,通常256M可以知足要求
2) 若别无选择,则只能将servlet的路径加到CLASSPATH中,但通常不建议这么处理
3. C Heap溢出
系统对C Heap没有限制,故C Heap发生问题时,Java进程所占内存会持续增加,直到占用全部可用系统内存
其余:
JVM有2个GC线程。第一个线程负责回收Heap的Young区。第二个线程在Heap不足时,遍历Heap,将Young 区升级为Older区。Older区的大小等于-Xmx减去-Xmn,不能将-Xms的值设的过大,由于第二个线程被迫运行会下降JVM的性能。
为何一些程序频繁发生GC?有以下缘由:
l 程序内调用了System.gc()或Runtime.gc()。
l 一些中间件软件调用本身的GC方法,此时须要设置参数禁止这些GC。
l Java的Heap过小,通常默认的Heap值都很小。
l 频繁实例化对象,Release对象。此时尽可能保存并重用对象,例如使用StringBuffer()和String()。
若是你发现每次GC后,Heap的剩余空间会是总空间的50%,这表示你的Heap处于健康状态。许多Server端的Java程序每次GC后最好能有65%的剩余空间。
经验之谈:
1.Server端JVM最好将-Xms和-Xmx设为相同值。为了优化GC,最好让-Xmn值约等于-Xmx的1/3[2]。
2.一个GUI程序最好是每10到20秒间运行一次GC,每次在半秒以内完成[2]。
注意:
1.增长Heap的大小虽然会下降GC的频率,但也增长了每次GC的时间。而且GC运行时,全部的用户线程将暂停,也就是GC期间,Java应用程序不作任何工做。
2.Heap大小并不决定进程的内存使用量。进程的内存使用量要大于-Xmx定义的值,由于Java为其余任务分配内存,例如每一个线程的Stack等。
2.Stack的设定
每一个线程都有他本身的Stack。
-Xss
|
每一个线程的Stack大小
|
按照基本回收策略分
引用计数(Reference Counting):
比较古老的回收算法。原理是此对象有一个引用,即增长一个计数,删除一个引用则减小一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是没法处理循环引用的问题。
标记-清除(Mark-Sweep):
此算法执行分两阶段。第一阶段从引用根节点开始标记全部被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法须要暂停整个应用,同时,会产生内存碎片。
复制(Copying):
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另一个区域中。算法每次只处理 正在使用中的对象,所以复制成本比较小,同时复制过去之后还能进行相应的内存整理,不会出现“碎片”问题。固然,此算法的缺点也是很明显的,就是须要两倍 内存空间。
标记-整理(Mark-Compact):
此算法结合了“标记-清除”和“复制”两个算法的优势。也是分两阶段,第一阶段从根节点开始标记全部被引用对象,第二阶段遍历整个堆,把清除未标记对象并 且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
按分区对待的方式分
增量收集(Incremental Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么缘由JDK5.0中的收集器没有使用这种算法的。
分代收集(Generational Collecting):基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不一样生命周期的对象使用不一样的算法(上述方式中的一个)进行回收。如今的垃圾回收器(从J2SE1.2开始)都是使用此算法的。
按系统线程分
串行收集:串行收集使用单线程处理全部垃圾回收工做,由于无需多线程交互,实现容易,并且效率比较高。可是,其局限性也比较明显,即没法使用多处理器的优点,因此此收集适合单处理器机器。固然,此收集器也能够用在小数据量(100M左右)状况下的多处理器机器上。
并行收集:并行收集使用多线程处理垃圾回收工做,于是速度快,效率高。并且理论上CPU数目越多,越能体现出并行收集器的优点。(串型收集的并发版本,须要暂停jvm) 并行paralise指的是多个任务在多个cpu中一块儿并行执行,最后将结果合并。效率是N倍。
并发收集:相对于串行收集和并行收集而言,前面 两个在进行垃圾回收工做时,须要暂停整个运行环境,而只有垃圾回收程序在运行,所以,系统在垃圾回收时会有明显的暂停,并且暂停时间会由于堆越大而越长。 (和并行收集不一样,并发只有在开头和结尾会暂停jvm)并发concurrent指的是多个任务在一个cpu伪同步执行,但实际上是串行调度的,效率并不是直 接是N倍。
分代垃圾回收
分代的垃圾回收策略,是基于这样一个事实:不一样的对象的生命周期是不同的。所以,不一样生命周期的对象能够采起不一样的收集方式,以便提升回收效率。
在Java程序运行的过程当中,会产生大量的对象,其中有些对象是与业务信息相关,好比Http请求中的Session对象、线程、Socket链接,这 类对象跟业务直接挂钩,所以生命周期比较长。可是还有一些对象,主要是程序运行过程当中生成的临时变量,这些对象生命周期会比较短,好比:String对 象,因为其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次便可回收。
试想,在不进行对象存活时间区分的状况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,由于每次回收都须要遍历全部存活对象,但实 际上,对于生命周期长的对象而言,这种遍历是没有效果的,由于可能进行了不少次遍历,可是他们依旧存在。所以,分代垃圾回收采用分治的思想,进行代的划 分,把不一样生命周期的对象放在不一样代上,不一样代上采用最适合它的垃圾回收方式进行回收。
如图所示:
虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。
年轻代:
全部新生成的对象首先都是放在年轻代的。年轻代的目标就是尽量快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(通常而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另一个Survivor区,当这个Survivor区也满了的时候,从第一个Survivor区复制 过来的而且此时还存活的对象,将被复制“年老区(Tenured)”。须要注意,Survivor的两个区是对称的,没前后关系,因此同一个区中可能同时 存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。并且,Survivor区总有一个是空 的。同时,根据程序须要,Survivor区是能够配置为多个的(多于两个),这样能够增长对象在年轻代中的存在时间,减小被放到年老代的可能。
年老代:
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。所以,能够认为年老代中存放的都是一些生命周期较长的对象。
持久代:
用于存放静态文件,现在Java类、方法等。持久代对垃圾回收没有显著影响,可是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候须要设置一个比较大的持久代空间来存放这些运行过程当中新增的类。持久代大小经过-XX:MaxPermSize=<N>进行设 置。
什么状况下触发垃圾回收
因为对象进行了分代处理,所以垃圾回收区域、时间也不同。GC有两种类型:Scavenge GC和Full GC。
Scavenge GC
通常状况下,当新对象生成,而且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,而且把尚且存活的对象移动到Survivor区。而后整理Survivor的两个区。这种方式的GC是对 年轻代的Eden区进行,不会影响到年老代。由于大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,因此Eden区的GC会频繁进行。因 而,通常在这里须要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC由于须要对整个对进行回收,因此比Scavenge GC要慢,所以应该尽量减小Full GC的次数。在对JVM调优的过程当中,很大一部分工做就是对于FullGC的调节。有以下缘由可能致使Full GC:
· 年老代(Tenured)被写满
· 持久代(Perm)被写满
· System.gc()被显示调用
·上一次GC以后Heap的各域分配策略动态变化
传说中的G1,传说中的low-pause垃圾收集。Java SE 6的update14版本中已经包含测试版,能够在启动时加JVM参数来启用
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC
http://www.blogjava.net/BlueDavy/archive/2009/03/11/259230.html
本文摘自《构建高性能的大型分布式Java应用》一 书,Garbage First简称G1,它的目标是要作到尽可能减小GC所致使的应用暂停的时间,让应用达到准实时的效果,同时保持JVM堆空间的利用率,将做为CMS的替代 者在JDK 7中闪亮登场,其最大的特点在于容许指定在某个时间段内GC所致使的应用暂停的时间最大为多少,例如在100秒内最多容许GC致使的应用暂停时间为1秒, 这个特性对于准实时响应的系统而言很是的吸引人,这样就不再用担忧系统忽然会暂停个两三秒了。
G1要作到这样的效果,也是有前提的,一方面是硬件环境的要求,必须是多核的CPU以及较大的内存(从规范来看,512M以上就知足条件了),另一方面是须要接受吞吐量的稍微下降,对于实时性要求高的系统而言,这点应该是能够接受的。
为了可以达到这样的效果,G1在原有的各类GC策略上进行了吸取和改进,在G1中能够看到增量收集器和CMS的影子,但它不只仅是吸取原有GC策略的优 点,并在此基础上作出了不少的改进,简单来讲,G1吸取了增量GC以及CMS的精髓,将整个jvm Heap划分为多个固定大小的region,扫描时采用Snapshot-at-the-beginning的并发marking算法(具体在后面内容详 细解释)对整个heap中的region进行mark,回收时根据region中活跃对象的bytes进行排序,首先回收活跃对象bytes小以及回收耗 时短(预估出来的时间)的region,回收的方法为将此region中的活跃对象复制到另外的region中,根据指定的GC所能占用的时间来估算能回 收多少region,这点和之前版本的Full GC时得处理整个heap很是不一样,这样就作到了可以尽可能短期的暂停应用,又能回收内存,因为这种策略在回收时首先回收的是垃圾对象所占空间最多的 region,所以称为Garbage First。
看完上面对于G1策略的简短描述,并不能清楚的掌握G1,在继续详细看G1的步骤以前,必须先明白G1对于JVM Heap的改造,这些对于习惯了划分为new generation、old generation的你们来讲都有很多的新意。
G1将Heap划分为多个固定大小的region,这也是G1可以实现控制GC致使的应用暂停时间的前提,region之间的对象引用经过 remembered set来维护,每一个region都有一个remembered set,remembered set中包含了引用当前region中对象的region的对象的pointer,因为同时应用也会形成这些region中对象的引用关系不断的发生改 变,G1采用了Card Table来用于应用通知region修改remembered sets,Card Table由多个512字节的Card构成,这些Card在Card Table中以1个字节来标识,每一个应用的线程都有一个关联的remembered set log,用于缓存和顺序化线程运行时形成的对于card的修改,另外,还有一个全局的filled RS buffers,当应用线程执行时修改了card后,若是形成的改变仅为同一region中的对象之间的关联,则不记录remembered set log,如形成的改变为跨region中的对象的关联,则记录到线程的remembered set log,如线程的remembered set log满了,则放入全局的filled RS buffers中,线程自身则从新建立一个新的remembered set log,remembered set自己也是一个由一堆cards构成的哈希表。
尽管G1将Heap划分为了多个region,但其默认采用的仍然是分代的方式,只是仅简单的划分为了年轻代(young)和非年轻代,这也是因为G1仍 然坚信大多数新建立的对象都是不须要长的生命周期的,对于应用新建立的对象,G1将其放入标识为young的region中,对于这些region,并不 记录remembered set logs,扫描时只需扫描活跃的对象,G1在分代的方式上还可更细的划分为:fully young或partially young,fully young方式暂停的时候仅处理young regions,partially一样处理全部的young regions,但它还会根据容许的GC的暂停时间来决定是否要加入其余的非young regions,G1是运行到fully-young方式仍是partially young方式,外部是不能决定的,在启动时,G1采用的为fully-young方式,当G1完成一次Concurrent Marking后,则切换为partially young方式,随后G1跟踪每次回收的效率,若是回收fully-young中的regions已经能够知足内存须要的话,那么就切换回fully young方式,但当heap size的大小接近满的状况下,G1会切换到partially young方式,以保证能提供足够的内存空间给应用使用。
除了分代方式的划分外,G1还支持另一种pure G1的方式,也就是不进行代的划分,pure方式和分代方式的具体不一样在下面的具体执行步骤中进行描述。
掌握了这些概念后,继续来看G1的具体执行步骤:
1. Initial Marking
G1对于每一个region都保存了两个标识用的bitmap,一个为previous marking bitmap,一个为next marking bitmap,bitmap中包含了一个bit的地址信息来指向对象的起始点。
开始Initial Marking以前,首先并发的清空next marking bitmap,而后中止全部应用线程,并扫描标识出每一个region中root可直接访问到的对象,将region中top的值放入next top at mark start(TAMS)中,以后恢复全部应用线程。
触发这个步骤执行的条件为:
l G1定义了一个JVM Heap大小的百分比的阀值,称为h,另外还有一个H,H的值为(1-h)*Heap Size,目前这个h的值是固定的,后续G1也许会将其改成动态的,根据jvm的运行状况来动态的调整,在分代方式下,G1还定义了一个u以及soft limit,soft limit的值为H-u*Heap Size,当Heap中使用的内存超过了soft limit值时,就会在一次clean up执行完毕后在应用容许的GC暂停时间范围内尽快的执行此步骤;
l 在pure方式下,G1将marking与clean up组成一个环,以便clean up能充分的使用marking的信息,当clean up开始回收时,首先回收可以带来最多内存空间的regions,当通过屡次的clean up,回收到没多少空间的regions时,G1从新初始化一个新的marking与clean up构成的环。
2. Concurrent Marking
按照以前Initial Marking扫描到的对象进行遍历,以识别这些对象的下层对象的活跃状态,对于在此期间应用线程并发修改的对象的以来关系则记录到remembered set logs中,新建立的对象则放入比top值更高的地址区间中,这些新建立的对象默认状态即为活跃的,同时修改top值。
3. Final Marking Pause
当应用线程的remembered set logs未满时,是不会放入filled RS buffers中的,在这样的状况下,这些remebered set logs中记录的card的修改就会被更新了,所以须要这一步,这一步要作的就是把应用线程中存在的remembered set logs的内容进行处理,并相应的修改remembered sets,这一步须要暂停应用,并行的运行。
4. Live Data Counting and Cleanup
值得注意的是,在G1中,并非说Final Marking Pause执行完了,就确定执行Cleanup这步的,因为这步须要暂停应用,G1为了可以达到准实时的要求,须要根据用户指定的最大的GC形成的暂停时 间来合理的规划何时执行Cleanup,另外还有几种状况也是会触发这个步骤的执行的:
l G1采用的是复制方法来进行收集,必须保证每次的”to space”的空间都是够的,所以G1采起的策略是当已经使用的内存空间达到了H时,就执行Cleanup这个步骤;
l 对于full-young和partially-young的分代模式的G1而言,则还有状况会触发Cleanup的执行,full-young模 式下,G1根据应用可接受的暂停时间、回收young regions须要消耗的时间来估算出一个yound regions的数量值,当JVM中分配对象的young regions的数量达到此值时,Cleanup就会执行;partially-young模式下,则会尽可能频繁的在应用可接受的暂停时间范围内执行 Cleanup,并最大限度的去执行non-young regions的Cleanup。
这一步中GC线程并行的扫描全部region,计算每一个region中低于next TAMS值中marked data的大小,而后根据应用所指望的GC的短延时以及G1对于region回收所需的耗时的预估,排序region,将其中活跃的对象复制到其余 region中。
G1为了可以尽可能的作到准实时的响应,例如估算暂停时间的算法、对于常常被引用的对象的特殊 处理等,G1为了可以让GC既可以充分的回收内存,又可以尽可能少的致使应用的暂停,可谓费尽心思,从G1的论文中的性能评测来看效果也是不错的,不过若是 G1能容许开发人员在编写代码时指定哪些对象是不用mark的就更完美了,这对于有巨大缓存的应用而言,会有很大的帮助,G1将随JDK 6 Update 14 beta发布。
http://www.iteye.com/topic/1119491
1.整体介绍:
CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来得到最短回收停顿时间的垃圾回收器。并发意味着除了开头和结束阶段,须要暂停JVM,其它时间gc和应用一 起执行。对于要求服务器响应速度的应用上,这种垃圾回收器很是适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用CMS。CMS采用的基础算法是:标记—清除。默认会开启 -XX :+UseParNewGC,在年轻代使用并行复制收集。
2.CMS过程:
初始标记 :在这个阶段,须要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到可以和"根对象"直接关联的对象,并做标记。因此这个过程虽然暂停了整个JVM,可是很快就完成了。
并发标记 :这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,因此用户不会感觉到停顿。
并发预清理 :并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象重新生代晋升到老年代, 或者有一些对象被分配到老年代)。经过从新扫描,减小下一个阶段"从新标记"的工做,由于下一个阶段会Stop The World。
从新标记 :这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。
并发清理 :清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。
并发重置 :这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。
CSM执行过程:
3.CMS缺点
总得来讲,CMS回收器减小了回收的停顿时间,可是下降了堆空间的利用率。
4.啥时候用CMS
若是你的应用程序对停顿比较敏感,而且在应用程序运行的时候能够提供更大的内存和更多的CPU(也就是硬件牛逼),那么使用CMS来收集会给你带来好处。还有,若是在JVM中,有相对较多存活时间较长的对象(老年代比较大)会更适合使用CMS。
jmap
jmap -heap pid (不能观察G1模式)
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 268435456 (256.0MB)
MaxNewSize = 268435456 (256.0MB)
OldSize = 805306368 (768.0MB)
NewRatio = 7
SurvivorRatio = 8
PermSize = 134217728 (128.0MB)
MaxPermSize = 134217728 (128.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 241631232 (230.4375MB)
used = 145793088 (139.03912353515625MB)
free = 95838144 (91.39837646484375MB)
60.33702133340114% used
Eden Space:
capacity = 214827008 (204.875MB)
used = 132689456 (126.54252624511719MB)
free = 82137552 (78.33247375488281MB)
61.7657236095752% used
From Space:
capacity = 26804224 (25.5625MB)
used = 13103632 (12.496597290039062MB)
free = 13700592 (13.065902709960938MB)
48.886444166411984% used
To Space:
capacity = 26804224 (25.5625MB)
used = 0 (0.0MB)
free = 26804224 (25.5625MB)
0.0% used
concurrent mark-sweep generation: (old区)
capacity = 1879048192 (1792.0MB)
used = 1360638440 (1297.6059341430664MB)
free = 518409752 (494.3940658569336MB)
72.41104543209076% used
Perm Generation:
capacity = 134217728 (128.0MB)
used = 65435064 (62.40373992919922MB)
free = 68782664 (65.59626007080078MB)
48.75292181968689% used
jmap -histo:live pid
num #instances #bytes class name
----------------------------------------------
1: 3148147 209172848 [B
2: 2584345 144723320 java.lang.ref.SoftReference
3: 2578827 123783696 sun.misc.CacheEntry
4: 781560 112544640 com.sun.net.ssl.internal.ssl.SSLSessionImpl
5: 1385200 89970592 [C
6: 783287 87807200 [Ljava.util.Hashtable$Entry;
7: 1421399 56855960 java.lang.String
8: 12 56828880 [Lsun.misc.CacheEntry;
9: 2343358 56240592 com.sun.net.ssl.internal.ssl.SessionId
10: 783185 50123840 java.util.Hashtable
11: 783094 50118016 java.lang.ref.Finalizer
12: 287243 36086720 [Ljava.lang.Object;
13: 263376 33712128 org.apache.commons.pool.impl.GenericObjectPool
jstat
jstat -gccause 31169 60000 1000
(sweep 1,2) (Eden) (Old) (Perm) (Young GC, GCTime)(Full GC, GCTime)
S0 S1 E O P YGC YGCT FGC FGCT GCT LGCC GCC
48.80 0.00 68.94 69.55 48.86 30202 725.319 51835 5083.298 5808.616 unknown GCCause No GC
47.98 0.00 37.47 69.61 48.86 30206 725.385 51835 5083.298 5808.682 unknown GCCause No GC
50.73 0.00 51.72 69.65 48.86 30210 725.459 51835 5083.298 5808.757 unknown GCCause No GC
0.00 50.02 82.67 69.60 48.84 30213 725.508 51836 5091.572 5817.081 unknown GCCause No GC
jstat -gcutil $pid
S0 S1 E O P YGC YGCT FGC FGCT GCT
74.79 0.00 95.15 0.86 37.35 2 0.112 0 0.000 0.112
O = old occupied
YGC = young gc time ( new part )
YGCT = young gc total cost time
FGC = full gc time ( old part )
FGCT = full gc total cost time
GCT = all gc cost time
jvisualvm
window下启动远程监控,并在被监控服务端,启动jstatd服务。
建立安全策略文件,并命名为jstatd.all.policy
grant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};
jstatd -J-Djava.security.policy=jstatd.all.policy -p 8080 &
-server -Xmx2g -Xms2g -Xmn512m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true
The (original) copying collector (Enabled by default). When this collector kicks in, all application threads are stopped, and the copying collection proceeds using one thread (which means only one CPU even if on a multi-CPU machine). This is known as a stop-the-world collection, because basically the JVM pauses everything else until the collection is completed.
The parallel copying collector (Enabled using -XX:+UseParNewGC). Like the original copying collector, this is a stop-the-world collector. However this collector parallelizes the copying collection over multiple threads, which is more efficient than the original single-thread copying collector for multi-CPU machines (though not for single-CPU machines). This algorithm potentially speeds up young generation collection by a factor equal to the number of CPUs available, when compared to the original singly-threaded copying collector.
The parallel scavenge collector (Enabled using -XX:UseParallelGC). This is like the previous parallel copying collector, but the algorithm is tuned for gigabyte heaps (over 10GB) on multi-CPU machines. This collection algorithm is designed to maximize throughput while minimizing pauses. It has an optional adaptive tuning policy which will automatically resize heap spaces. If you use this collector, you can only use the the original mark-sweep collector in the old generation (i.e. the newer old generation concurrent collector cannot work with this young generation collector).
UserParallelGC使用了更高效的算法,用于处理大规模内存>10G场景,提供了大吞吐量功能。可是,同时在老生代,只能使用串行的标记清除方法。
老生代,必须作fullgc,必须从root开始全面标识收集。