JVM之ClassLoader与内存管理总结篇

看完《分布式Java应用基础与实践》第3章后,对JVM的工作机制有了初步的认识。总结主要包括两个部分:ClassLoader与JVM内存管理。

ClassLoader

Java中的类加载器主要可以分为两大类:1.系统提供的类加载器;2.开发人员自定的类加载器。其中,系统提供的类加载器包括以下3个:

1. bootstrap classloader:主要负责加载Java中的核心类库(对应系统路径为sun.boot.class.path)。

2. extension classloader:主要负责加载JAVA_HOME/jre/lib/ext(java.ext.dirs)中指定的JAR包

3. system classloader:系统类加载器,负责加载CLASSPATH中的类

ClassLoader加载类特点

ClassLoader采用全盘委托机制,若当一个ClassLoader加载一个Class的时候,这个Class所依赖的Class也由该ClassLoader负责。

ClassLoader加载类的流程



 
ClassLoader加载机制如上图所示。其中extension classloader的Parent ClassLoader为null,则说明它的父类加载器为bootstrap classloader,system classloader的父加载器为extension classloader。

JVM内存管理

JVM内存分配

 JVM的内存空间可以划分为:方法区、堆、本地方法栈、PC寄存器以及JVM方法栈。下图为JVM方法区与堆的结构图。


 方法区(也称持久代):主要用于存放要加载类的名称、修饰符、类中的静态变量、定义为final类型的常量、方法、Field等数据。通过设置-XX:PermSize及-XX:MaxPerSize可以指定持久代的最小值和最大值。当持久代加载类的信息过多的时候,持久代的内存空间可能会被占满,在Full GC的作用下,仍没有足够的空间存储需要加载类的信息,则此时会抛出java.lang.OutMemoryError:PermGen space。

(Heap):堆由Eden区、Survivor区(Survivor区中包含两块大小相同的s0和s1)、旧生代组成。堆的大小可以通过设置-Xms与-Xmx参数(-Xms指定JVM启动时申请的最小堆内存,-Xmx指定JVM可申请的最大堆内存,一般情况下将堆的最小内存和最大内存设置成一样,避免在运行时频繁调整堆的大小)。

新生代由Eden区、S0、S1(S0、S1又称作From或To区)组成。Eden区主要用于存放new出来的对象,S0或S1用于复制YGC时存活的对象。可以通过设置-Xmn参数来指定新生代的大小,同时可以通过-XX:SurvivorRatio来调整Eden Space及Survivor Space的大小。

旧生代主要存放多次GC后仍然存活的对象,但新建的对象也可能在旧生代中直接分配内存。主要存在两种情况:1.大对象,可以通过设置启动参数-XX:PretenureSizeThreshold来代表对象超过多大时就不在新生代分配;2.大数组对象,且数组中无引用外部对象。旧生代所占用的内存空间大小为-Xms对应的值减去-Xmn对应的值。 当旧生代的空间不足时,会执行Full GC,当Full GC后空间仍然不足,则会抛出java.lang.OutofMemoryError:java heap space错误。

GC回收内存的几种收集器

JVM通过GC来回收堆和方法区中的内存,GC的基本原理是找到程序中不再被引用的对象,然后回收这些对象所占用的内存空间。主要的收集器包括计数收集器和跟踪收集器。跟踪收集器主要包括:复制、标记-清除(M-S)、标记压缩(M-C)三种实现方法。

 GC回收器 


 
JVM根据对象的存活时间的特点,在不同的内存空间中采用不同的GC(如上图所示)。 

新生代GC

新生代GC称为Minor GC或Young GC(YGC)。那在什么情况下会触发YGC呢?当Eden区没有足够的空间容纳下新创建出的对象时则触发YGC。YGC的特点是:都采用复制(Copy)算法。当发生YGC时,将Eden区与From Survivor区中存活的对象拷贝到To Survivor区中,若当To Survior的空间不足时,会将对象直接复制到旧生代中。不同的GC回收器之间处理拷贝存活对象到To Survior区中还存在着不同,如串行GC会将多次YGC仍然存活的对象拷贝到旧生代中(这个阈值可以通过-XX:MaxTenuringThreshold设置)。

接下来简要介绍下新生代GC具体种类。

SerialGC:串行GC。适用场景为对暂停时间要求不严格,在客户端下适用,可以通过在Java启动参数中加入-XX:+UseSerialGC来指定使用串行GC。该GC会stop the world(意思是在进行GC时会停止应用程序中的其他线程的运行,下同)。

并行GC(ParNew):并行GC采用多线程进行垃圾回收,但在进行垃圾回收时,也会stop the world。通常并行GC配合旧生代使用CMS GC。同时可以设置-XX:+UseParNewGC来强制使用并行GC。

旧生代&持久代GC

旧生代或持久代在以下条件下会触发GC:1.旧生代空间不足,由于在YGC时,对象的转入以及创建大对象、大数组时Eden区空间不足;2.持久代空间不足。其中比较常见的GC有串行GC、并行GC、CMS GC。

串行GC:适用场景在客户端中使用,采用MSC算法。

并行GC:适用场景在服务端中使用,采用MSC算法。在服务端中并行GC会被自动选择,同时可以通过设置-XX:UseParallelGC指定使用并行GC。

CMS GC:适用场景是对响应时间大于吞吐量,采用M-S算法。但缺点是会产生内存碎片。CMS GC的主要过程如下:1.Initial Mark:识别所有被引用的对象,该步会stop the world; 2.concurrent Mark:并发标记存活的对象; 3.Remark:因为并发标记和程序一起运行,所以在并发标记结束的时候,不能保证引用的对象被标记,所以需要重新标记一次,该步会stop the world; 4.concurrent sweep。可以通过增加-XX:+UseConcMarkSweepGC来启动CMS GC。 

Full GC

除旧生代与持久代配置CMS GC外,旧生代或持久代的GC都会引起Full GC(会对新生代、旧生代、持久代进行GC),以下几种情况都会引起Full GC

1. 直接调用System.gc

2. 旧生代空间不足

3. 持久代空间不足

4. CMS GC时出现promotion failed和concurrent mode failure

上述简要介绍了新生代GC和旧生代、持久代GC。看懂GC日志、以及通过常用命令排查内存分配问题在此就不做详细介绍。分享下介绍GC的其他文章吧:

《分布式JAVA应用基础与实践》第3章

http://iamzhongyong.iteye.com/blog/1333100 

http://iamzhongyong.iteye.com/blog/1989829

http://blog.csdn.net/fenglibing/article/details/6321453

猜你喜欢

转载自hawking-ye.iteye.com/blog/2226973