这篇文章的前言,给大家分享一个生产环境的实践经验:部署线上系统时,JVM堆内存大小是不是越大越好?先说明一个前提。本文主要讨论Kafka和Elasticsearch这两个分布式系统的在线部署,不是普通的Java应用系统。1、是否依赖Java系统自身的内存来处理数据?首先,不管是自己开发的Java应用系统,还是一些中间件系统,我们在实现的时候都需要选择是否基于自己Java进程的内存来处理数据。大家应该都知道,Java、Scala等编程语言底层都是依赖JVM的,所以只要使用了JVM,就可以考虑将大量的数据放在JVM进程的内存中。让我举一个例子。大家应该还记得之前讲过消息中间件系统吧。比如系统A要给系统B发送消息,中间就需要依赖一个消息中间件。系统A必须先将消息发送给消息中间件,然后系统B从消息中间件消费消息。请看下图。大家应该都知道消息发送到消息中间件之后,有一种处理方式,就是先把这个数据缓存在自己的JVM内存中。然后,过一段时间,就会从自己的内存中刷新到磁盘中,这样消息就可以持久化了,如下图。2、依赖Java系统自身内存有什么缺陷?如果使用类似上述的方法依赖Java系统自身的内存来处理数据,比如设计一个内存缓冲区来缓冲高并发写入的大量消息,那么就存在缺陷。.最大的缺陷其实是JVM的GC问题。这个GC就是垃圾回收。这是它是什么的简要说明。大家可以想一下,如果一个Java进程中总是塞满了很多数据,这些数据是用来在内存中缓冲的,但是过一段时间这些数据就会写入磁盘。那么写入磁盘后,这些数据还需要保存在内存中吗?显然是没有必要的,而此时,它会依赖JVM的垃圾回收机制,回收内存中不需要的数据,释放内存空间腾出空间。但是JVM垃圾回收的时候,有一种情况叫做stoptheworld,就是它会把你的worker线程停止,让它去执行垃圾回收。这时候他在收集垃圾的时候,有可能你的中间件系统就跑不起来了。比如你给他发一个请求,他可能无法响应你,因为他接收请求的工作线程已经停止了,现在后台的垃圾回收线程正在回收垃圾对象。大家看下图。虽然JVM的垃圾回收器在不断的进化和发展,从CMS到G1,都可以尽可能的减少垃圾回收的影响,减少工作线程的停顿。但是如果完全依赖JVM内存来管理大量的数据,在垃圾回收时或多或少总会有影响。所以特别是对于一些大数据系统和中间件系统来说,JVM的GC(GarbageCollector,垃圾回收)问题真的是最头疼的问题。3.优化依赖OSCache而不是JVM,所以像Kafka、Elasticsearch这样的分布式中间件系统,虽然也是基于JVM,但是都选择依赖OSCache来管理大量的数据。也就是说,它是由操作系统管理的内存缓冲区,而不是依赖于JVM自身的内存来管理大量的数据。具体来说,比如Kafka,如果你写一个数据到Kafka,实际上是直接写到磁盘文件中。但是磁盘文件在写入之前,实际上会进入os缓存,也就是操作系统管理的内存空间。一段时间后,操作系统会选择将其oscache的数据刷新到磁盘中。那么后面消费数据的时候,实际上会先从os缓存(内存缓冲区)中读取数据。相当于基于os缓存写数据和读数据,完全依赖操作系统层面的内存区域,读写性能非常高。除此之外,还有一个好处,就是不需要依赖自己的JVM缓存大量的数据,可以避免复杂耗时的JVM垃圾回收操作。看下图,其实就是一个典型的Kafka运行流程。那么,比如Elasticsearch,作为最流行的分布式搜索系统,也采用了类似的机制。大量的os缓存依赖于缓存大量的数据,然后在查找查询的时候,也可以先从os缓存(内存区)中读取数据,这样可以保证非常高的读写性能.4、根据老司机的经验,依赖os缓存的系统的JVM内存应该是越大越好?那么现在可以进入我们的主题了。比如上面提到的kafka、elasticsearch等系统,在线上生产环境部署的时候,你就知道它们非常依赖oscache来缓冲大量的数据。那么,给它们分配JVM堆内存大小是不是越大越好呢?很明显不是。假设你有一台内存为32GB的机器。现在如果不了解情况,如果傻傻的认为给JVM多分配内存比较好。比如,16G的堆内存空间给了JVM,那么os缓存的剩余内存可能就不到10GB了,因为其他程序本身还占用了好几GB的内存。如果是这样的话,会导致你写磁盘的时候oscache能容纳的数据量非常有限。比如一共需要写入20G的数据到磁盘,现在os缓存中只能放10GB的数据,然后其他10GB的数据就只能放磁盘了。此时读取数据时,至少有一半的读取请求必须从磁盘中读取,不能从os缓存中读取。谁跟你说os缓存只能放10G的一半大小另一半数据在磁盘上,没办法,如下图。这时候你的请求有一半是从磁盘读取数据,必然导致性能不佳。所以很多人在使用Elasticsearch的时候都会遇到这样的问题。他们总觉得ES的阅读速度慢。当数亿条数据写入ES时,读取需要几秒。那不会需要几秒钟吗?如果部署ES集群,给JVM留太多内存,给os缓存留几GB,导致上亿数据大部分在磁盘,不在os缓存,大量磁盘被读取阅读结束时,需要几秒钟是正常的。5、正确的做法是合理的给oscache更多的内存给场景。因此,在部署像Kafka、Elasticsearch这样的生产系统时,应该给JVM比如6GB或者几GB的内存。因为它们可能不需要消耗太多的内存空间,也不依赖于JVM内存管理数据。当然,设置多少取决于你的精准测压和优化。但是对于这种类型的系统,你应该为os缓存留出足够的内存空间。比如32GB内存的机器,完全可以给os缓存留出20G多的内存空间。那么假设你的机器一共写入了20GB的数据,可以全部驻留在os缓存中。那么后面查询数据的时候,你可以从os缓存中读取所有数据,完全依赖内存,那你的性能肯定是毫秒级的,不可能几秒就完成一次查询。整个过程如下图所示:因此,在将任何技术引入在线生产系统时,建议大家首先对技术的原理乃至源码有深入的了解,知道其具体工作流程是怎样的,然后针对生产环境的部署方案进行主动合理的设计,确保最佳的生产性能。
