也说JVM
JVM的内存区域模型
新生代:
程序新创建的对象都是从新生代分配内存,新生代由EdenSpace和两块相同大小的SurvivorSpace(通常又称S0和S1或From和To)构成,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整EdenSpace及SurvivorSpace的大小。
老年代:
用于存放经过多次新生代GC任然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:①.大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。②.大的数组对象,切数组中无引用外部对象。
对象访问
对象访问会涉及到Java栈、Java堆、方法区这三个内存区域。
举例:Object objectRef = new Object();
"Object objectRef” 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现。
而“new Object()”这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定。另外,在java堆中还必须包括能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些数据类型存储在方法区中。
句柄访问方式:
java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
指针访问方式:
reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据
垃圾收集算法
跟踪收集器---(可达性分析算法)[ReachabilityAnalysis]
跟踪收集器采用的为集中式的管理方式,全局记录对象之间的引用状态,执行时从一些列GC Roots的对象做为起点,从这些节点向下开始进行搜索所有的引用链,当一个对象到GC Roots 没有任何引用链时,则证明此对象是不可用的。
下图中,对象Object6、Object7、Object8虽然互相引用,但他们的GC Roots是不可到达的,所以它们将会被判定为是可回收的对象。
可作为GC Roots 的对象包括:(GC roots 包括1、栈引用,2、方法区静态对象引用,3、方法区常量引用的对象,4、本地方法栈JNI的引用对象)
虚拟机栈(栈帧中的本地变量表)中的引用对象。
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI的引用对象
主要有复制、标记清除、标记整理三种实现算法。。
方法区的回收(可视为permanent generation)
1 常量的回收,如“abc”只要没有引用就可以回收。
2 类的回收(是类信息,不是对象记住)
2.1 当所有的该类的实例都已经回收了
2.2 加载该类的classLoader已经回收
2.3 该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方反射
1. 标记 - 清除算法
标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。
它的主要缺点:
①.标记和清除过程效率不高
②.标记清除之后会产生大量不连续的内存碎片。
2. 复制算法
它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。
主要缺点:
内存缩小为原来的一半。
3. 标记 - 整理算法
标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。
主要缺点:
在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。
分代收集算法(generationcollection)
l 新生代(YOUNG generation)
朝生夕灭————大批对象死亡(存活的很少)用复制算法
l 老年代(Old generation)存活率高,用标记-删除或者标记-整理
垃圾收集器
HotSpot JVM收集器
1、安全点(safe point)——进入GC
2、安全区域(safe region)——在线程Sleep时进行GC
(链接表示配合使用)
CMS相关解释; 其中初始标记、重新标记这两个步骤任然需要停顿其他用户线程。初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快,并发标记阶段是进行GC ROOTS 根搜索算法阶段,会判定对象是否存活。而重新标记阶段则是为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间会被初始标记阶段稍长,但比并发标记阶段要短。
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
CMS收集器的优点:并发收集、低停顿,但是CMS还远远达不到完美,器主要有三个显著缺点:
CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。
CMS收集器无法处理浮动垃圾,可能出现“ConcurrentMode Failure“,失败后而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,
即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial
Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。
最后一个缺点,CMS是基于“标记-清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,比如说大对象,内存空间找不到连续的空间来分配不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full GC之后,跟着来一次碎片整理过程。
垃圾收集器参数总结
-XX:+<option> 启用选项
-XX:-<option> 不启用选项
-XX:<option>=<number>
-XX:<option>=<string>
参数 |
描述 |
-XX:+UseSerialGC |
Jvm运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收 |
-XX:+UseParNewGC |
打开此开关后,使用ParNew + Serial Old的收集器进行垃圾回收 |
-XX:+UseConcMarkSweepGC |
使用ParNew + CMS + Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用。 |
-XX:+UseParallelGC |
Jvm运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old的收集器组合进行回收 |
-XX:+UseParallelOldGC |
使用Parallel Scavenge + Parallel Old的收集器组合进行回收 |
-XX:SurvivorRatio |
新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Subrvivor = 8:1 |
-XX:PretenureSizeThreshold |
直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
-XX:MaxTenuringThreshold |
晋升到老年代的对象年龄,每次Minor GC之后,年龄就加1,当超过这个参数的值时进入老年代 |
-XX:UseAdaptiveSizePolicy |
动态调整java堆中各个区域的大小以及进入老年代的年龄 |
-XX:+HandlePromotionFailure |
是否允许新生代收集担保,进行一次minor gc后, 另一块Survivor空间不足时,将直接会在老年代中保留 |
-XX:ParallelGCThreads |
设置并行GC进行内存回收的线程数 |
-XX:GCTimeRatio |
GC时间占总时间的比列,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge 收集器时有效 |
-XX:MaxGCPauseMillis |
设置GC的最大停顿时间,在Parallel Scavenge 收集器下有效 |
-XX:CMSInitiatingOccupancyFraction |
设置CMS收集器在老年代空间被使用多少后出发垃圾收集,默认值为68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70 |
-XX:+UseCMSCompactAtFullCollection |
由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效 |
-XX:+CMSFullGCBeforeCompaction |
设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用 |
-XX:+UseFastAccessorMethods |
原始类型优化 |
-XX:+DisableExplicitGC |
是否关闭手动System.gc |
-XX:+CMSParallelRemarkEnabled |
降低标记停顿 |
-XX:LargePageSizeInBytes |
内存页的大小不可设置过大,会影响Perm的大小,-XX:LargePageSizeInBytes=128m |
Client、Server模式默认GC
|
新生代GC方式 |
老年代和持久代GC方式 |
Client |
Serial 串行GC |
Serial Old 串行GC |
Server |
Parallel Scavenge 并行回收GC |
Parallel Old 并行GC |
Sun/oracle JDK GC组合方式
|
新生代GC方式 |
老年代和持久代GC方式 |
-XX:+UseSerialGC |
Serial 串行GC |
Serial Old 串行GC |
-XX:+UseParallelGC |
Parallel Scavenge 并行回收GC |
Parallel Old 并行GC |
-XX:+UseConcMarkSweepGC |
ParNew 并行GC |
CMS 并发GC |
-XX:+UseParNewGC |
ParNew 并行GC |
Serial Old 串行GC |
-XX:+UseParallelOldGC |
Parallel Scavenge 并行回收GC |
Parallel Old 并行GC |
-XX:+UseConcMarkSweepGC |
Serial 串行GC |
CMS 并发GC |
对象内存回收与分配
对象优先在Eden分配。(Eden区与survivor比为8:1)
大多数情况下,对象优先在新生代Eden区域中分配。当Eden内存区域没有足够的空间进行分配时,虚拟机将触发一次 Minor GC(新生代GC)。Minor GC期间虚拟机将Eden区域的对象移动到其中一块Survivor区域。
大对象直接进入老年代
所谓大对象是指需要大量连续空间的对象。虚拟机提供了一个XX:PretenureSizeThreshold参数,令大于这个值的对象直接在老年代中分配。
长期存活的对象将进入老年代
虚拟机采用分代收集的思想管理内存,那内存回收时就必须能识别那些对象该放到新生代,那些该到老年代中。为了做到这点,虚拟机为每个对象定义了一个对象年龄Age,每经过一次新生代GC后任然存活,将对象的年龄Age增加1岁,当年龄到一定程度(默认为15)时,将会被晋升到老年代中,对象晋升老年代的年龄限定值,可通过-XX:MaxTenuringThreshold来设置。
Minor GC 和Full GC区别
新生代GC(MinorGC):指发生在新生代的垃圾收集动作,因为对象大多都具备朝生夕灭特性,所以Minor GC非常频繁,回收速度也比较快。
老年代GC(Major GC/ Full GC):指发生在老年代中的GC,出现Major GC后,经常会伴随至少一次的 Minor GC。Major GC的速度一般会比Minor GC慢10倍以上。
参考:《深入JVM》——周志明
- 1楼oChenXiaoZuo1昨天 23:41
- 排版问题图片都没有上传上去