草庐IT

大数据框架中的Java虚拟机优化

信息时代风之影 2023-03-28 原文
近年以来,大数据应用取得了长足的进展,各种大数据处理框架也应运而生,并得到了业界的高度认可,如Hadoop生态、Spark系列、Flink、Cassandra、Hive等等。这类编程模型通常采用分治的思想,将大的数据处理作业拆分为多个小的计算任务,分配到分布式集群中的不同节点中运行,然后将结果汇聚起来,得出最终结果。

由于使用习惯等关系,业内主流的大数据处理框架都采用Java语言进行编写。原因在于以下几点:

1、很多程序开发人员对于Java语言比较熟悉,使用起来轻车熟路;

2、Java提供了便捷的自动内存管理机制,避免了用户在处理内存过程中可能出现的问题;

3、Java基于JVM运行的特性使得Java程序可以一次编写,多处运行,摆脱分布式集群中不同操作系统和硬件处理框架带来的束缚,使得开发者可以更加专注于代码逻辑的开发;

4、Java语言拥有成熟的社区和丰富的编程资源,可以实现快速开发,出现问题也可以快速寻求帮助。

正是基于这些优点,Java语言成为了目前最主流大数据编程技术。但是,在实际使用的过程中,开发者们发现了一系列JVM相关的性能瓶颈,主要包括以下几个方面:

1、垃圾回收(GC)占用时间长,在一些大数据应用中,GC时间甚至可以达到总执行时间的50%;

2、GC频率高,造成任务执行频繁暂停,应用吞吐率降低,响应延迟升高;

3、GC算法挤占应用线程CPU资源,存在GC线程竞争时,大数据应用执行时间增长可达60%;

4、数据对象在分布式节点间传输时需要序列化和反序列化,在某些大数据应用中,用时占比可达30%;

5、JVM冷启动时需要大量的类加载和代码即时编译工作,在应用执行中的用时可达数十秒;

6、JVM运行和维护需要内存消耗,在内存紧张的情况下,可能因为内存耗尽或内存碎片触发OOM错误。

总的来说,这些问题的产生,可以归纳为以下一些原因:

1、内存使用压力增大

与普通的Java应用不同,大数据应用是“内存密集”型的,应用的内存使用量更大,在大数据处理框架下,JVM的内存使用压力具体来源于:

(1)大数据应用数据计算和存储产生的大量内存消耗,大量数据在计算过程中需要同时被读取到内存中,而一些应用为了更进一步加快处理速度,将中间数据的聚合和可重用数据也缓存在内存当中,这决定了JVM在执行大数据应用时将面对更大的内存使用量;

(2)数据在JVM堆内存当中以对象的形式存储需要额外的内存占用,对象在JVM当中的数据结构包含了对象头以及对其它对象的引用,而数据本身在对象中的空间占比往往不超过一半。这些对象的外壳伴随着数据缓存在内存当中,也需要占用相当数量的空间;

由于JVM垃圾回收机制的原因,会经常触发全局暂停,而这个问题很难通过简单的增减内存大小来解决,如果降低内存大小,GC的触发频率会增加,对象被扫描和的去的次数增加,应用程序的吞吐量相应降低。可用内存不足还会影响到应用的正常缓存和处理机制,甚至引发内存溢出。而如果提升内存大小,单次GC则需要处理更多的数据对象,平均的暂停时间加长,应用程序的最大延迟相应增加。对于周期性标记扫描的GC算法而言,还会在最终触发GC之前消耗更多CPU时序进行不必要的标记。

2、内存使用模式变化

大数据应用中数据在内存当中保留的时间周期与普通应用不尽相同。在传统应用中,堆内存中创建的绝大部分对象在产生之后不久就不再被使用,经典的GC算法正是基于这种内存使用模式,将堆内存进行粗粒度的年代划分,绝大部分瞬时对象会在针对年轻代的Minor GC当中很快被清理。而大数据应用产生的对象有两种,一种是由控制大数据处理框架运行逻辑的代码产生的,即控制路径对象,它们的内存使用模式一般依旧符合弱世代假设。另一种是输入数据和计算中间数据在大数据处理框架中封装生产的,统称为数据路径对象。这种对象的内存使用模式要更加复杂,它们可能在内存中长时间累积或缓存,也可能在一个迭代轮次后被清理和输出。通常来说,数据路径所创建的对象数量远超控制路径。传统GC算法并不能适应大数据环境下内存使用模式的这种变化,原因在于:

(1)当前GC算法下,长时间存活的数据路径对象最终都会晋升到老年代中,它们在数次Minor GC当中幸存并最终晋升的过程中,需要在内存中多次移动。而对象移动是GC循环当中最耗时的部分,每一次移动都意味着内存读写,而内存位置的改变也需要对相关引用的指针进行更新。考虑到数据路径对象的数量极为庞大,整个晋升过程会消耗大量CPU时间,触发多次GC暂停;

(2)数据路径对象在晋升到老年代之后,在作业执行的时间尺度上,短时间内也不会被回收。传统的GC算法不会考虑这些对象的存活时间,在涉及到老年代空间的MajorGC或者Mixed GC之前还是会整个堆内存空间进行标记扫描,这些标记扫描过程对于长时间存活的数据对象来说是不必要的。当长时间存活对象占用老年代的比例过高,每次传出较大代价的Major GC就只能回收有限大小的空间,可能造成GC频繁触发,部分缓存数据被迫转移到磁盘,甚至出现OOM错误,浪费大量的CPU时间和全局暂停时间,影响到应用执行效率。

3、JVM与上层框架存在隔阂

大数据处理框架将计算任务分配到各个执行器JVM节点之后,并不会干预JVM的具体执行过程,每个执行器JVM独立运行,并不感知分布式集群中其它执行器JVM的执行情况,作业的整体进度,以及集群和节点的内存资源使用情况,只是根据自身的运行状态作出触发GC,调整堆内存,进行代码即时编译等决策,而这些决策从历史和全局的角度上观察可能并不是最优的。原因在于:

(1)JVM不清楚任务执行产生的数据对象特征,例如对象数量、内存占用大小、生命周期等,只能根据弱世代假说,对所有对象进行统一的管理。由于大数据应用产生的大量对象长时间存活,JVM的内存管理效率会受到严重影响,而这些对象本可以通过大数据框架对用户代码和数据流的全局静态分析进行甄别。

(2)大数据处理框架并不考虑JVM具体的内存管理机制,将所有JVM节点的内存当做连续的全局地址空间,但是实际上JVM在GC算法下对堆内存采取分代管理,存在非连续区域,对象在内存中离散分布,另外大数据处理框架在采用全局地址空间的物理架构下,可能产生大量跨节点对对象引用,给JVM的GC任务带来了远程内存访问的负担。

(3)大数据处理框架下的JVM之间不清楚彼此的运行情况,如果大数据操作需要在各个JVM之间同步,由于JVM独立进行GC决策,大数据操作的执行就可能被不同的JVM的GC连续打断,另外由于互相不感知,处于同一物理节点的JVM之间可能内存资源分配不合理,而大数据框架在相关问题上缺少统筹协调。

因此,开发者们需要针对这些问题产生的原因,进行针对性优化。我们下次文章将会继续讨论这个问题。

有关大数据框架中的Java虚拟机优化的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  6. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  8. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  9. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  10. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

    我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

随机推荐