精简版
0
0
0
云栖社区 > 阿里巴巴中间件 > 博客 > 正文
中间件小哥 2019-12-11 11:10:11 浏览 2813
云栖社区
java
服务器
性能
线程
系统监控
高并发
内存泄漏
离线计算
分布式系统
展开阅读全文
内存回收一直是 Java 的痛点
用 Java 无法做出类似 Redis 这样的产品. Java 的内存回收机制使我们在编写代码时不需要关注对象的回收, 同时加大了内存回收的消耗, 标记复制需要做内存拷贝, 标记清除算法则需要 stop the world . 所以我们在使用缓存的时候, 量稍微大一些就需要借助类似 Redis 这样的中间件帮我们处理了. 作为 Javaer , 我们享受了自动内存回收的安逸, 同时也需要多了解下内存优化的方法.
为什么 FGC 停不下来了
什么情况下会 GC
为了了解我们的系统为什么会不停 FGC , 我们需要先了解一下系统什么情况下会 GC . 在 Jvm 层面, 当我们 new 一个对象的时候, Jvm 会先在堆区分配对象需要的内存, 这个时候如果内存不够的话, 就需要 GC 了, GC 的返回结果就是对象的空间地址. Jvm 会先进行 ygc , 也就是我们通常说的标记复制, 如果 ygc 之后依然申请不到空间, 就会进行 FGC 了. 同理, 如果 FGC 之后依然没有足够的空间, 就会循环的进行 FGC , 直到申请到足够的空间.
导致不停的 FGC 的原因
如上文所讲, FGC 有可能发生在你的每一行代码. 如果 FGC 之后依然没有足够的空间, 就会不停的 FGC , 直到申请到足够的空间. 同时 JVM 会限制在抛出 OutOfMemory 错误之前在 GC 中花费的 VM 时间的比例. 系统频繁 F 大致有五种情况:
内存泄漏
请求处理变慢导致同时申请内存的线程太多
metaspace 耗尽
常量池将堆区占满
堆外内存耗尽
在一个高并发的系统中, 多数 FGC 是请求处理变慢导致的. 假设单机承受 tps 是 1w, 正常情况下处理一个请求的时间是 1ms , 那同一时刻并行的请求数量仅为 10 . 如果性能发生抖动, 每个请求处理的时间增加到 100ms , 那同一时刻并行的请求数量就会增加到 100 个. 每个线程在处理请求的时候都会 new 一些对象出来, 长时间存活的线程会造成类似内存泄漏的效果, 将系统的内存耗尽. 同时 FGC 也会加剧系统性能的开销, 使系统变得更慢, 产生雪崩.
如何让系统 FGC 之后仍然能活下来
杜绝内存泄漏
内存泄漏产生的原因以及解决办法网上有很多资料, 这里就不写了. 内存泄漏造成系统瘫痪的频率很高, 有些系统定时从数据库拉取配置信息缓存到集合中, 但是 set 不小心写成了 list , 最终在新增元素的时候内存溢出了. 养成良好的编程习惯, 多关注些细节, 就能避免很多未知的问题.
并发限制: 防止系统被撑死
每台服务器都有并行处理请求的上限, 不管请求处理的多快, 超过上限之后就会被撑死, 对高并发的请求做好并发数限制是保持系统稳定的必要条件. 需要注意的是, 有一些系统在拒绝过多的请求时, 也会做一些降级逻辑, 降级逻辑也是有性能开销的, 同样需要做并发限制, 如果降级的请求超过并发限制, 将不进行降级逻辑直接抛出异常.
自适应限流: 防止系统被摸死
我们需要自适应限流有两个原因:
每台服务器所处的环境是不一样的
有些服务器和离线计算的 vm 混部在一起, 有些部署在实体机, 有些部署在新老型号的机器上, 每台服务器能承受的 qps 并不完全一样. 统一配置分布式系统中每台服务器限流阀值, 要么发挥不出每台服务器应有的作用, 要么在高 qps 的情况下一些比较慢的服务器宕机, 所以用服务器作为限流粒度是最合适的.
设置了正确的限流阀值, 也可能被摸死
当单机承受的 QPS 6~20 倍于限流的流量时, 拒绝一次请求的开销就无法忽略不记了. 譬如春晚活动有些系统设置了正确的限流也被 6~20 倍于限流的流量冲垮. 这种死法称为被摸死. 应对这种情况, 我们可以做的是在受到 6~20 倍的大流量时, 动态减少限流的阀值. 比如系统最开始接受 1000qps ,5000 的拒绝流量过来会把系统摸死, 这个时候我们调整系统的阀值, 限流设置到 100 , 被摸死的阀值就可以高一些, 这样就算有 6000 个请求进来, 我们系统也可以保证活下来.
阿里有结合算法动态调整单机限流阀的产品, 已经对外公布了, 感兴趣的同学可以搜一下淘系技术公众号中的 诺亚自适应限流 的相关内容.
异常流量监控: 防止长尾请求拖垮系统
我们盯系统监控的时候通常会关注 99 分位的数据, 但如果设置了合理的限流, 系统依然被流量打挂, 就要从那百分之一的长尾数据入手了. 有些长尾数据对系统的影响会非常大. 想象如果一个 put 请求传过来几十兆的数据, 对 Java 是极为不友好的, 很有可能产生 FGC , 让请求变慢, 导致一系列问题.
总之, 磨刀不误砍柴工, 当我们的系统因为 FGC 一次又一次重启的时候, 不如花时间了解下系统产生性能问题的原因, 将产生问题的那根针拔掉, 晚上睡个安稳觉, 白天更加充满活力的挖新坑. 希望每个程序员手里都是一个稳定的系统.
来源: https://yq.aliyun.com/articles/738772