查看原文
其他

R大,王卓,陆传胜,占小狼等JVM专家聚一起干啥呢? | 中生代-拍拍贷JVM闭门会议纪要

2017-05-04 刘建刚 中生代技术

各位中生代的读者们,May the 4th be with you!

只是

不过还好有中生代大牛陪你过节,请看来自中生代上海的活动纪实!



2017年4月22日下午2点左右,拍拍贷上海会议室陆陆续续来了一些人。

仔细一看,原来是王卓(阿里JVM专家),陆传胜(阿里JVM专家),占军(占小狼简书地址:

),韩国忠(百度资深研发),何涛(唯品会架构师),李洋(点评架构团队产品负责人),拍拍贷的开发专家:尹作龙、费永军、王云戈。

还有拍拍贷资深架构师Leo和唯品会架构师刘建刚。

他们连上了视频会议,视频对面赫然是传说中RednaxelaFX(简称R大)


(还不知道R大的,看这里补课 https://www.zhihu.com/question/24923005)

Leo作为主人毫不客气的做了主持人,总书记则由刘建刚担任!


上一张R大的靓照。

原来这帮JVM专家们在一起搞

闭!门!交!流!面!基!


(小编心想,这帮大牛加上江南白衣,寒泉子等是不是华语JVM天团?JVMboys?)


首先R大分享了C2编译器优化

同学们专注的眼神。

第一部分,R大详细解释了C2编译器优化的工作原理。

解释器记录着执行次数和循环次数,超过了某个固定阈值,会交给C1编译器。C1在编译过程中,会profile,并且会记录执行次数和循环次数,超过了某个固定阈值后,会交给C2编译,然后会稳定在C2.除非C2做了激进的编译优化,导致结果不再正确。这个时候会抛弃C2编译结果,重新交给解释器执行。


首先确定基本块分布,基本块是最长直线执行代码。比如,if else语句块,就是if和else两个基本块。C2找基本块的同时,就会转成内部的表现形式,IR。


C2编译器执行第一步是parse字节码,也是C2编译器的核心部分,它包括常量折叠,identity和global value numbering。常量折叠指的是,几个常量在一起的操作或者运算的结果是固定的,那么可以编译阶段确定结果,那这个操作或者表达式可以直接用这个结果替代。identity是指的x+y先出现,后面y+x又出现了一次,那编译器认为这两个是相同的,就会用前面的x+y替换掉后面的y+x。global value numbering是指的两个符号常量x,y的计算,比如x+y,那后面的再出现一个x+y,会给第一个出现的x+y一个标号,后面x+y就会用第一个标号替换,这是子表达式消除的常用手法。接下来,C2会去做死代码消除。死代码消除是指的删除那些被优化的代码,比如被常量折叠后的无用路径,无效的分支路径。


接下来,是优化过程。优化过程是迭代global value numbering,循环优化,再是条件常量传播,再一次的迭代global value numbering和循环优化。这里的条件常量传播是指的,比如有一个常量折叠或者优化,使得if条件变成常量,而这个优化可能使得其它的变量被优化成常量,可能会有进一步的优化,最后可能一大段代码被优化成一个常量。这就是所谓的条件常量传播。


第二部分,是针对内部表现形式(IR)的案例介绍。

这里针对讨论较多Exception相关的概念做个介绍。Safepoint记录一些信息,循环结尾跳开头处,未内联方法调用,GC所关心指针的位置等等。异常抛出肯定是从Safepoint,JAVA异常首先会清空栈空间,然后把抛出异常对象指针放到栈空间里面,然后走到catch或者finally块。异常分为同步异常和异步异常。同步异常比如空指针,除以0这种,条件发生时候,立刻抛出异常。异步异常,常见的例子是nio使用的unsafe。unsafe操作的时候,出现问题并不会立刻抛出异常,而是等到下一个safepoint,才把这个异常跑出去,这就是异步异常。因为unsafe操作本来就是为了高性能,Safepoint增多会降低性能,所以才用下一个Safepoint来抛出这个异步异常,来提高性能


最后,讲述了C2的一些待优化点,比如缺少平台相关优化。

R大还推荐给大家两篇论文:

Cliff Click的PhD论文,也是C2的源头:

另一篇详细介绍C2编译原理的论文:


===================我是分割线================


阿里JVM专家传胜分享了 怎么更好对使用硬件资源


1. 多租户可以降低成本,使用场景,1tomcat跑多个应用,更好资源隔离,节省成本。同时解决热部署对时候资源释放不干净,应用升级发布可以在2min内解决。


2. 3d xpoint memory

由于gc pause长,我们可以通过减小堆减少pause。但是,这些对象可以放哪里呢?可以把对象放到堆外,但是堆外需要额外序列化。intel开发了3d xpoint memory,是一种介于内存和ssd之间硬件,比dram慢一点,但是非易失的。现在已经有JEP提出,把堆里面内容已到xpoint里面去,则样可以提升JVM性能。由于硬件存储非易失,那重启时候,内容不会丢,重启过程将大大加快。另外个问题,xpoint也是有大小,放对象的,也是需要gc的。那这块区域怎么做gc呢?那就是放一些不需要被回收的,比如基础数据等等,让它不需要回收。


3. LLC 分区

每个CPU都有自己的L1,L2缓存,每个socket多个CPU共享一个L3也就是一个LLC缓存。LLC缓存它是介于内存和L2缓存之间的缓存,使用时候,数据会首先由内存加载到LLC来,然后供CPU使用。APP运行时候,LLC是APP数据。但是GC后,LLC里面到数据不一定是APP数据的,就需要先把数据加载到LLC。YGC频繁时候,那APP的数据就需要不停的从内存加载到LLC里面来,对RT很敏感的应用对这种频繁加载来说,很难忍受。现在有个JEP,就是对LLC分区,保持一个分区给某个进程或者某个线程使用,保持数据不会因为GC被踢出LLC,那么将大大减少LLC重新加载时间。


===================我是分割线================


唯品会架构师何涛分享了 JVM实战

1. CPU掉坑问题排查


背景:唯品会网关采用netty开发,使用来24个worker线程来工作,使用log4j2 记录日志。


问题描述:在做压测对时候,出现了大量异常日志的时候,ygc变长,从20ms增加到100ms,然后每次ygc晋升到old区的对象都很多。


问题分析:通过jmap -histo和堆dump,发现堆中主要是两种throwable对象,一种有异常堆栈,一种没有异常堆栈。而异常都是通过log4j异步输出使用的。由于在输出过程中,需要去获取其异常堆栈,需要调用native方法,所以整个输出过程特别慢,就慢慢导致ringbuffer堆积。ringbuffer满了之后,log4j写本地buffer线程就被阻塞了,导致cpu掉坑。对于jvm来说,由于这些对象全部在新生代,做gc的时候,会越来越多的throwable对象被复制,所以造成了ygc时间越来越长,每次也有大量对象晋升都old区。


解决方法:


1. 定义异常常量供复用


2. 手动填充异常,屏蔽native方法获取异常慢方法


3. 网关系统在haproxy下的异常ygc时间


问题描述:在网关系统测试的时候,分别使用haproxy和lvs作为前端负载均衡的情况下,lvs使用连接数比较多,请求量比较大大情况下,其ygc时间为20ms,远小于请求少连接少的haproxy


问题分析:通过查看代码发现,haproxy使用的是短连接,而lvs使用的是长连接。在haproxy这种场景下,使用了实现了finalize方法serverscoketimpl类。由于finalize方法实现者需要被finalizer类的实例所引用,在gc的时候,由于有引用存在,不会被回收,只会在young区拷贝。gc之后,发现finalizer实例引用的实现finalize的对象已经没有引用来源类,整个finalizer会被加入一个queue,这个queue会被一个地优先级的finalizer线程所运行,这个线程会运行finalizer所指向对象的finallize方法。由于优先级较低,执行比较慢,所以可能即使在这个queue中的finalizer对象所指向的对象也可能被继续搬运。由此,造成了ygc的时间 较长。


大家在自由讨论时间,讨论了一些JVM目前存在的问题和改进方向,例如,Value type等。


听完各位专家分享,已经是傍晚。拍拍贷邀请专家们共进晚餐,把酒言欢!


人是铁,饭是钢,人生大幸莫过于饭前听一次技术干货分享!


正所谓:一入JVM深似海,从此优化是最熟悉的陌生人!

觥筹交错后,继续踏上优化不归路,下次闭门会再见!(全文完)




活动推荐

上海CTO聚会,详情识别二维码吧

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存