JVM堆内存监测的一种方式,性能调优依旧任重道远

本文涉及的产品
应用实时监控服务ARMS - 应用监控,每月50GB免费额度
日志服务 SLS,月写入数据量 50GB 1个月
简介:

上月,由极客邦、InfoQ和听云联合主办2016 APMCon中国应用性能管理大会圆满落下帷幕。会上,Java冠军Martijn Verburg进行了一场Java and the Machine的分享,讨论了为什么数据分析至关重要。他有着十多年Java经验,目前是创业公司jClarity的CEO,jClarity是一款采用统计和机器学习来探究性能问题根源的方案。会后,InfoQ还专访Martijn以进一步了解沟通。

JVM堆内存及一种监测方式

在讨论Martijn的团队如何进行堆内存监测之前,我们先回顾下JVM的工作机制。JVM是一种对计算机的抽象行为,是它保证了Java程序的运行。每一个运行的Java程序都对应着一个JVM实例。JVM的结构如下图

Java把内存划分成两种:一种是栈内存,一种是堆内存。栈与堆都是Java用来在RAM中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。即每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。

自从Java1.3之后,Oracle出台的JRE就包含了一个名为HotSpot的JVM,它将Java的对象按照世代管理并存放在堆的内存中,以期可以更好地管理堆内存中的对象,包括内存的分配以及回收。共划分为三个世代:年轻代、老年代、永久代。永久代(Permanent Generation)则存放的是类的定义和相关元数据。但是在Java 8中该区域已经被移除。专家分析称此举更有利于性能调优。

现有版本保留的两个世代为年轻代(Young Generation)和老年代(Old Generation)。年轻代为创建的短期对象,失效之后很快会被垃圾回收。该区又被划分为Eden和两个Survivor区域。老年代存放的多数为存活时间较长的对象。其中堆的各个区之间的比例分配有默认值,但是可以通过参数指定。垃圾回收GC分为两种Minor GC、Full GC;Minor GC发生频繁,但是仅针对年轻代。

垃圾回收之后会对JVM造成一定影响,年老代的占用空间曲线如下图:

上图来自jClarity对堆的年老代占用空间监测图,jClarity是一款Java的性能监测工具,由Martijn、另外两位资深Java专家和一位机器学习工程师共同实现。Martijn分享了jClarity如何进行堆内存的监测他首先列举了垃圾回收之后,通常情况下堆内存中的老年代(Tenured/Old Generation)的内存占用曲线,一般而言,会先后发生两个陡然增加的高峰。

基于以上现象的信息, Martijn和他的团队开展了他们的性能监测方案。Martijn他们采取的做法是先收集尽可能多的点,然后只保留老年代的数据,对垃圾回收造成的两个脉冲式波峰进行了过滤,这时再对这些真正的数据点进行建模抽象,最后保留出了一条曲线。

这条曲线就是对监测的Java程序内存的变化趋势,该曲线会以50Mb/小时的速率增长,据此推测出何时发生内存泄露。jClarity还有很多其他功能,相比于传统的指标统计方式,Martijn称团队产品的特点在于:高级统计+机器学习。

Java和JVM面临怎样的困境?

Martijn还分享了他对Java现状和JVM性能调优的担忧和思考,他认为现在Java和JVM面临下面五个问题:

程序只能写一次,但是却要在各种地方跑
这意味着Java需要解决来自各方面的差异:
CPU的差异——什么时候可以放入缓存中呢?什么时候可以被重新排序呢?
文件系统的差异——不同操作系统对文件的符号链接有不同机制
显示设备的差异——硬件更新速度很快,几乎无法追上
原生库支持的差异——很难存在所有的原生库皆一致的情况
操作系统线程管理的差异——线程的规划方式很不相同
此外,面对正在迅速发展AR、VR,Java缺少真正的GPU支持,这同样是一个短板。模型对存储的强需求
JVM太谨慎了,他总想做对的事情,但是为此不得不在性能上的妥协。锁机制加强了正确性,但是在性能上付出了巨大的代价。
锁机制决定了Java序列化的工作区。因为Java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。结合利特尔法则(Little's law)和阿姆达尔定律(Amdahl’s law),这种方式影响到了并行存储的性能。垃圾回收机制下的扩展性
JVM需要维护活的对象,这意味着:堆需要更大空间以存放更多的对象;垃圾回收机制需要花时间去辨认哪些是活的对象;在垃圾回收过程中需要耗费时间进行堆的维护。
其次,Java中没有值类型(value type 和reference type的区别),没有结构体:这造成了大量低效能的对象创建。容器和虚拟化的支持
Java没法获取虚拟化数据。Java和JVM的思考模式是建立在物理裸机上的,信息缺失的情况下会进行一些错误选择。并且,没有对Docker等容器技术的直接支持,这是新时代的另一个短板。

关于Java和JVM性能调优的思考

除了上述的整体层面的挑战之外,Java的性能本身又很难监测。必须结合其他的指标来间接把控:CPU,内存;接口I/O,网络I/O;虚拟化和容器化等。可是一旦获得了这些指标,又带来了大数据的问题。因为我们盲目地收集了过多的数据,这造成了巨大的性能损害,因为收集、传输、存储每个过程都是一种消耗。

要记住目的是分析获得信息,而不是收集指标。但是从指标数据到提炼出有用信息很难,Martijn认为要做好性能调优需要明白规律和原理(如上文所提及的Little's law和Amdahl’s law),理解硬件、操作系统、Java工作原理还要读懂代码,并且已经有了基于大量数据的分析经验。

Martijn认为未来性能监测的趋势是高级统计和机器学习方式的结合,这种模式将取代传统的单纯指标采集模式。

对话Martijn

InfoQ:通常来说,JVM层面的APM工具并不适用于生产环境。那为什么您称jClarity可以?

Martijn:与其他工具相比,首先在JVM层面上我们获取更少的数据。 jClarity之所以可以更少地获取,是因为我们采用了机器学习的办法,辨别除了哪些才是真正有用的数据,我们称之为“信号”,余下的数据我们称之为噪声。在收集数据过程中,一旦检测噪声,我们立刻对过滤。

其次我们还会尽可能避免从JVM本身获取数据,取而代之从JVM的日志中(如GC日志、safepoint日志)等获取数据。目前我们还在和Google合作,在尝试怎样从JVM之外,获得更多的信息。但是,总体而言,jClarity用于生产环境是没有问题的。

InfoQ:为什么还要收集JMV的日志之外的数据呢?

Martijn:因为JVM日志并不能给我们足够的信息。你可以从JVM日志中获得,或者通过JMX接口。不过,你也可以通过设定一个Java或者原生的agent来获得更特定的信息;但这是一种过重的做法,通常而言并不推荐。很多人包括Oracle在内都意识到了这个问题,但是完全解决有待时日。

InfoQ:数据收集的过程是否对用户来说是透明的呢?是否支持ASM字节码织入技术呢?

Martijn:是的。底层的数据,我们不仅仅从Java中获得,还会从操作系统中获取。比如,当用户在Linux上运行,那么他还会看见收集到的CPU、内存使用率等信息。

对于有特殊需求的用户,他们是可以采用ASM,此外我们也提供一个开发阶段使用的库,但是建议用户小心使用,因为很容易会产生操作不当。

InfoQ:数据收集之后,jClarity根据机器学习出来的成果进行了处理,能否和我们分享下机器学习的事情?

Martijn:在我被许可的范围内,因为机器学习是我们的机密模块。不过我可以分享这里非常重要的一点:我们有大量多环境的用户数据,用户们的程序也是多种多样,如网页程序、视频流程序等;我们会施加不同的网络压力,这样我们得到了数据训练集。这些数据集是专家人工操作产生的。同时在实时收集处理数据的时候,我们也会进行机器学习。比如发现Java性能受到影响,机器学习认定最重要一个因素就是GC垃圾回收,那么接下来就是调查GC;如果GC没有问题,那么就会在机器学习成果的指引下开始下一个因素的勘察。总体而言,机器学习的决定了发生问题时排查的路径。

机器学习这部分的研发工作我们做了两年,也很感谢这些用户为我们付费并且同意我们这样做。我们的一些用户拥有超大规模,这种情况下,已经无法指望人工对数据进行分析;所以对于我们来说,机器学习是唯一的出路,唯一的方式可以让我们继续支持用户。

InfoQ:APM工具的挑战有哪些?

Martijn:首先是IT环境复杂性的加剧。你的代码不再跑在你自己的机器和环境中,你无法做到100%确定你的程序和代码是怎样被管理的。另外一个挑战就是网络是不稳定的。在有线光纤专用网络上,你不需要和其他人共享;但是在公用云上,网路流量徒增的情况时有发生,开发人员不得不在代码层面上面对这个挑战,APM工具也必须理解并迎面这个问题。目前,还没有哪个APM工具可以胜任这两个挑战,所以目前这还是一个非常有趣有待研究的领域。

InfoQ:您在演讲中有提到,Java创立之初硬件并没有今天这样复杂,那么是不是说Java已经不再适合今天了呢?你认为Java语言的独特魅力在哪里呢?

Martijn:“Java将死”的留言每年都有。Java语言这么多年,经常遇到新兴语言的挑战,但是新兴语言很快就会发现自己也处于一个类似的局面。我认为Java依然是一个合适的语言,JVM正在尝试在各种情况下都做到最好。我们可以看到Go、RUST语言变得越来越火,但是Oracle正在非常努力地攻克这点,包括更频繁地发布Java,也很想快速地跟上包括支持GPU、其他新型硬件设备等。所以说挑战是有的,但是令人高兴的是很多大公司都愿意继续努力提高Java。我希望Java社区可以更开放些,可以欢迎更多的公司参与,比如阿里巴巴,阿里巴巴可能是全球上最大的重度Java研发群。

Java的魅力在于易写性、强可读性。不像现在的一些语言,你不需要严格地按照某种规范来写代码。就算代码写完五年之后,新来的人依然可以读懂代码。还有就是Java丰富的框架和库。甚至这些框架和库的魅力可以和Java语言本身相媲美。

本文转自d1net(转载)

相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
相关文章
|
26天前
|
存储 缓存 监控
|
27天前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
36 4
|
2天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
15 6
|
2天前
|
监控 安全 程序员
如何使用内存池池来优化应用程序性能
如何使用内存池池来优化应用程序性能
|
27天前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
54 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
5天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
17天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
44 10
|
17天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
26天前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
46 2
|
27天前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
50 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
下一篇
无影云桌面