250 likes | 745 Views
淘宝 GCIH : 直接共享 Java 对象,提升 GC 性能. // 阿里程序员 *2 // 莫简豪(坤谷) // 孙宇(洪熙) // jvm.taobao.org // GreenTeaJUG. 目标. 不做产品推广 不讲项目故事 分享技术实践 开阔技术思路 欢迎讨论指正. 议程. 术语 问题 分析 方案比较: JNI 、 MappedByteBuffer 方案选择: GCIH 解决 基础:对象模型 、 CMS GC 算法 如何移入 :遍历对象图 阻止 GC 扫描 如何共享:对象共享 、元信息处理
E N D
淘宝GCIH:直接共享 Java 对象,提升 GC 性能 // 阿里程序员*2 //莫简豪(坤谷) // 孙宇(洪熙) // jvm.taobao.org // GreenTeaJUG
目标 • 不做产品推广 • 不讲项目故事 • 分享技术实践 • 开阔技术思路 • 欢迎讨论指正
议程 • 术语 • 问题 • 分析 • 方案比较:JNI、 MappedByteBuffer • 方案选择:GCIH • 解决 • 基础:对象模型 、CMS GC算法 • 如何移入 :遍历对象图 • 阻止GC扫描 • 如何共享:对象共享 、元信息处理 • 实际问题:分布式单点、部署 、异常处理 • 结果 • POC:SideDataDemo • 实际结果
术语 • JVM:Java虚拟机,从操作系统看,是一个用户进程 • Java Heap:存放Java对象的堆 • GC:垃圾回收,回收Java Heap中没用的Java对象 • Full GC:GC的一种,全停机,时间长,对性能影响大,应尽量避免 • Hadoop:Java实现的分布式计算系统,高容错,高可扩展 • master节点:控制节点,少数相对高配的机器 • slave节点:计算和存储节点,大量相对低配机器 • map/reduce:在slave节点上运行的基本计算单元,每个map/reduce是一个JVM进程 • slot数:可以并发的map/reduce数 • 作业:一个完整的计算任务,分解成多组map/reduce,分布到slave上并发执行
问题 • 淘宝搜索某系统(基于Hadoop)slave内存瓶颈 • 集群大,增加物理内存贵 • 若调大slot数,则需要调小Java Heap,结果FullGC严重,作业执行慢 • 若为避免FullGC,调大JavaHeap,则需调小slot数,结果还是作业执行慢 • 一些只读对象在每个JVM中都有一份。内存冗余严重 • 如果JVM间共享这些对象,可以节约内存并提升GC性能
分析(一)方案比较 • JNI:读取频繁,调用JNI效率低 • MappedByteBuffer:实现依赖JNI,同上 • 将需要共享的Java对象移出Java堆,放在GC看不到的共享堆(GCInvisible Heap) • 不经过JNI调用 • 没有序列化和反序列化 • GC性能提高 • 数据更集中,cache命中率提高
分析(二)方案选择:GCIH(1) • 对象的直接移动 • 程序中直接访问GCIH内的Java对象,完全没差异 • 阻止GC收集GCIH中的对象,减少GC收集的负载
分析(二)方案选择:GCIH(2) Sharable Objs • Java对象可在JVM进程间直接共享 共享前 共享后
解决(一)基础:Java对象模型(1) • header主要用于表示对象的当前的状态,也用于存储一些gc及存储对象的age,hash值等数据。 • klass是Java对象的元数据指针,指向perm区的klass对象。
解决(一)基础:CMS 算法简介 • init-mark:标记old和perm区的root对象,stop the world • concurrent-mark:并发递归标记init-mark的标记结果 • remark:并发重新标记concurrent-mark阶段后某些对象状态的更新而遗漏标记的对象,stop the world • sweep:实际回收内存的操作 • reset
解决(二)如何移入:遍历对象图(2) • 接口 object moveIn(object obj) • 从根对象开始,根据对象的引用关系,递归moveIn • 如果某个对象被moveIn则将其在GCIH的地址encode到该对象的header上,并设置header的最低2位(二进制位)为01 • 如果发现要移入的的header的最低2位为01,则从其header中decode出其在GCIH内部的地址,并直接修改相关的引用为正确的地址
解决(三)阻止GC扫描 • GC过程:从root对象出发,标记活的对象,同时将这个对象push到queue,以便于该gc线程后续处理其引用到的其他对象 • GCIH修改了GC过程:当某个对象的地址在gcih地址空间内的时候,直接返回,而且不将此对象push到gc线程的queue • 要求对象从jvm角度来看必须只读
解决(四)如何共享 • 对象共享 • mmap共享内存 • 不同JVM的GCIH映射到相同基地址 • 元信息klass的处理(难点) • klass指针用于FastSubTypeCheck,不可以用二级索引 • 保证klass基地址一致,并preload共享klass • 阻止gc unload 共享对象的klass
解决(四)实际问题 • 分布式单点 • 创建并初始化共享GCIH只能由一个JVM完成(单点),与分布式系统思想不一致 • 通过文件锁,多JVM并发抢锁,抢到锁的做初试化移入后释放锁,其他JVM直接映射GCIH • 部署 • GCIH修改JVM,部署阻力大 • 同时部署原版和GCIH版JVM • Hadoop框架使用原版JVM,需共享的作业通过参数设置其map/reduce使用GCIH版JVM • 异常处理 • 启动异常快速exit并打印原因 • 运行异常抛Java异常 • 由于双JVM部署,用户可选择异常时自动回滚到原版JVM(到目前没回滚过) • 只读保护异常,若用户不小心修改共享对象,测试中可快速发现,避免产生难以发现的bug
结果(一)POC:SideDataDemo(1) • reduce需要一个只读的SideData对象 • 共享前,由于内存成为瓶颈,只能开3个reduce,再开就OOM • GCIH共享后可以解决这瓶颈,提高了并发度
结果(一)POC:SideDataDemo(2) • 机器空闲内存约1.4G,只读对象SideData大约400M • 开3个reduce task内存还够用,Job complete • $ bin/hadoop jar ../GCIHDemo2.jar com.taobao.demo.SideDataDemo input output 3 • …11/05/09 14:56:50 INFO mapred.JobClient: map 100% reduce 100% • 11/05/09 14:57:10 INFO mapred.JobClient: Job complete: … • 开4个reduce task则内存不足,FAILED • $ bin/hadoop jar ../GCIHDemo2.jar com.taobao.demo.SideDataDemo input output 4 • …11/05/09 15:03:03 INFO mapred.JobClient: Task Id : attempt_201105091440_0002_r_000001_1, Status : FAILED • java.io.IOException: Cannot run program "bash": java.io.IOException: error=12, Cannot allocate memory …
结果(一)POC:SideDataDemo(3) • 将对象SideData移入GCIH • 设置child JVM与GCIH相关的启动参数 • 开4个reduce task可以正常执行,top命令观察到4个同时运行的task • PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND • 8650 kungu.mj 20 0 1338m 429m 393m S 33.3 24.6 0:17.25 java • 8682 kungu.mj 19 0 1400m 427m 393m S 33.3 24.4 0:15.82 java • 8618 kungu.mj 23 0 1339m 427m 393m S 32.9 24.4 0:15.79 java • 8714 kungu.mj 18 0 1336m 427m 393m S 32.6 24.4 0:15.29 java • 验证的计算结果也正确
结果(二)实际结果 • 淘宝搜索某系统(基于Hadoop)使用GCIH解决了内存瓶颈 • 在单机多reduce共享只读对象,每台机器共享约2GB的只读对象(Map、List、Set、String、Integer等) • 单机slot越多,节约内存越多 • 性能也越高,因为这2GB的对象只需创建和初始化一次 • 已经在生产上稳定运行多时,并通过了淘宝双十一、双十二的考验
Q&A 谢谢!