专栏名称: 刘超的通俗云计算
刘超,网易云解决方案首席架构师,代码级略懂OpenStack、Hadoop、Docker、Lucene、Mesos等开源软件,曾出版《Lucene应用开发揭秘》,个人博客可搜索popsuper1982。
目录
相关文章推荐
51好读  ›  专栏  ›  刘超的通俗云计算

JVM与GC

刘超的通俗云计算  · 公众号  · 架构  · 2017-08-13 23:44

正文

最近处理客户高并发应用的时候,经常会遇到GC的问题,于是是时候把原理学习过的JVM和GC的内容拿出来温习一下了。


一些准备


-XX:+


-XX:-


-XX:


-Xmx:HeapDumpPath=./dump.core堆内存快照存储区



JVM标准结构



类的加载机制


一:装载(load)

由ClassLoader负责加载; (ClassNotFoundException)

二:链接(Link)

校验(verify)、准备(Prepare)、初始化静态变量赋默认值;

(NoClassDefFoundError)

三:初始化

执行静态初始化代码、赋值静态变量。


ClassLoader


一:BootStrap ClassLoader

由Sun用C++实现此类,JDK启动时负责加载jre/lib/rt.jar里的所有

class,及java规范中定义的接口及实现;

二:Extension ClassLoader

JVM用于加载扩展功能的jar包,如DNS.jar

Jdk中的名称:sun.misc.Launcher$ExtClassLoader

三:System ClassLoader

加载启动参数中指定ClassPath的Jar包

Jdk中的名称: sun.misc.Launcher$AppClassLoader

一般的程序使用的都是AppClassLoader

四:自定义ClassLoader

用户也可以自定ClassLoader用于加载其他路径的Jar如网络上加载



类的执行机制


虽然类已加载成功且静态属性及实例对象皆以创建,但是,执行静态方法或者实例方法时仍需要对JVM字节码进行处理。


JVM有以下三种执行方式:

(1):解释执行

(2):编译执行

(3):反射执行


解释执行


采用经典的冯诺依曼FDX循环方式,即获取下一条指令,解码并分派,然后执行。


解释执行的优点:

简单、占资源少、启动速度快


解释执行的缺点:

效率低




编译执行

JDK将字节码编译成机器码,编译在运行时进行,故称作JIT编译器(Just-in-time)•策略:对执行频率频繁的代码使用编译执行,对执行频率不高的仍采用解释执行


编译执行的两种方式:

(1)ClinetCompiler:又称C1较轻量级,java  -client

(2)ServerCompiler:  又称C2 较重量级,java -server


Client Compiler


占用内存较少,适合于桌面应用•优化方法:

一:方法内联,将调用的方法指令直接植入到当前方法中;

二:去虚拟化,针对只有一个实现类的方法;

三:消除冗余,在编译时进行代码清理;


Server Compiler


C2采用了大量优化技巧,占内存较多,适合于服务器应用;

优化方法:标量替换、栈上分配、同步削除;

默认当CPU个数超过两个且内存超过2G自动采用Server模式,否则为Client模式,但在32位的windows上始终都是Client模式,可通过java-server强制使用Server模式,或者java-client强制使用client模式。


JVM内存管理


JVM内存结构图:



JVM方法区


用于存放类信息、类的属性、方法等信息。


又称为持久代PermanentGeneration,默认最小值16MB,最大值64MB。持久代在一定条件下也会被GC(垃圾回收),当空间不够时,会抛出OutOfMemory错误信息。


可通过-XX:PermSize  -XX:MaxPermSize来指定最小最大值。


PC寄存器与方法栈


每个线程均会创建自己的PC寄存器和方法栈。

PC寄存器存放每条指令的地址。

方法栈为线程私有,当方法执行完毕时,其栈帧所用内存会自动被回收。

当栈空间不足时会抛出StackOverflowError,可通过设置-Xss来指定其大小,以避免空间不够用。


JVM堆


堆用于存储对象实例和数组值。

在32位机上最大2GB,在64位机则无限制

可通过-Xms设置最小值,默认为物理内存的1/64但小于1GG

通过-Xmx设置最大值,默认为物理内存的1/4但小于1GB

默认当空余堆内存小于40%时,JVM会增大Heap到最大值,可通过-XX:MinHeapFreeRatio=来设定这个比例。

当空余空间大于70%时,JVM会减小到最小值,可通过-XX:MaxHeapFreeRatio=来设定这个比例。

为避免运行时JVM频繁调整Heap大小,通常将-Xms与-Xmx设成相同值。



JVM堆结构




新生代 new generation


  • 大多数情况下Java创建的对象都从新生代分配。

  • 新生代由Eden Space和两块大小相同的Survivor Space(又称S0和S1或者From和To)构成。

  • 可通过-Xmn指定新生代的大小。

  • -XX:SurvivorRatio来调整Eden和Survivor的大小比例。


旧生代 Old Generation


  • 用于存放在新生代中多次回收仍然存活的对象。

  • 有两种情况新建的对象会直接在老生代分配:

    • 通过设置-XX:PretenureSizeThreshold,当新对象大小超过设定值时直接在老生代分配,但是当新生代采用Parallel Scavenge GC时该设置无效。另一种是大的数组对象,且数组中无引用外部对象。

  • 旧生代的大小为-Xmx减去-Xmn的值。


内存回收-GC


  • 所谓内存回收就是我们所熟知的GC(Garbage Collection)垃圾回收。

  • JVM通过GC来回收堆和方法区的内存,GC的原理为首先找到内存中不再被引用的对象,然后回收。

  • 通常采用收集器的方式来实现GC,主要的收集器有引用计数收集器和跟踪收集器。


引用计数收集器


引用计数收集器通过记录对象的引用次数,当次数为零时,可进行回收。

但当出现循环引用时该收集方法则无法回收:



所以引用计数方式不适合面向对象这种有复杂引用关系的语言,SunJDK在实现GC时也未使用过此方式。


跟踪收集器

  • 跟踪收集器采用集中式的管理方式,全局记录数据的引用状态。基于一定的条件触发例如:空间不足,定时。

  • 执行时需要从根对象来扫描对象间的引用关系,这会造成应用的暂停。

  • 主要实现算法有:赋值(Copying)、标记-清除(Mark-Sweep)、标记-压缩(Mark-Compact)。


复制算法-Copying


复制算法采用的方式为从根集合扫描出存活的对象,并将存活的对象复制到一块新的完全未使用的空间。



当存活对象少时,Copying算法是很高效的,其代价是需要一块新的存储区和进行对象移动。


标记-清除 Mark-Sweep


  • 标记-清除采用的方式为从根集合开始扫描,对存活的对象进行标记,标记完成后,对未被标记的对象进行扫描并回收。

  • Mark-Sweep不需进行对象移动,且仅对不存活的对象进行处理。在空间存活对象较多的情况下较为高效,但会形成内存碎片。




标记-压缩 Mark-Compact


  • 标记-压缩与标记-清除的不同在于,对回收的内存空间进行压缩,即所有存活的对象都往左端空闲的空间进行移动,并更新引用对象的指针。

  • 该算法的好处在于不产生内存碎片,减少OutOfMemory的风险,缺点是移动对象的成本较高。



新生代可用GC


  • 新生代的大多数对象存活时间较短,故采用了Copying算法。

  • 新生代的GC又叫Minor GC。

  • Minor GC有三种回收方式:串行GC(Serial GC)、并行回收GC(Parallel Scavenge)、并行GC(ParNew)。


串行GC(Serial GC)


  • 串行GC从根集合扫描存活的对象。JVM认为根对象为当前线程栈上引用的对象、常量、静态变量、传到本地方法还未被释放的引用。

  • 串行GC扫描存活的对象,然后将存活的对象复制到S0或S1中(S0与S1同时只能有一个被使用,另一个为空)。

  • 为了避免扫描过程中引用关系的改变,JDK采用了暂停应用的方式。

  • 通常只有经过几次MinorGC仍存活的对象才放入旧生代中。该次数可通过-XX:MaxTenuringThreshold设置(只在串行与ParNew方式下生效默认值为15)。但该项并不是唯一的规则。串行和ParNew在每次GC后计算可存活的次数,规则为累计每个age对象占用的内存,如果累计超过SurvivorSpace的一半,则以age为准,否则,以MaxTenuringThreshold为准。

  • 如果ToSpace空间满则直接转入旧生代。




SerialGC采用单线程方式,适用于单CPU,对暂停时间要求不高的应用,也是Client级别(CPU小于2个或物理内存小于2GB,或32位Windows机器上)的GC方式,可通过-XX:+UseSerialGC来强制执行。







请到「今天看啥」查看全文