本文作者Pierre是一名有10多年经验的高级系统架构师,他的主要专业领域是Java EE、中间件和JVM技术。根据他多年的工作实践经验,他发现许多性能问题都是由Java堆容量不足和调优引起的。下面他将和大家分享非常实用的5个Java堆优化技巧。
1.JVM:对难以理解的东西产生恐惧感
千万不要以为,通过配置,调优,就可以排除那些你所不明白的问题。有些人认为Java程序员不需要知道内部JVM内存管理。毫无疑问,这种观点明显是错误的,如果想拓宽知识面和提升排除故障能力,你就必须要了解和学习一下JVM内存管理。
对于Java或者是Java EE新手来说,Java Heap调优和故障排除是一项非常有挑战的工作。下面会提供一些典型的案例场景:
客户端环境面临着有规律的OutOfMemoryError错误并且对业务造成了很大的影响。
你的开发团队要在如此大的压力下去解决这个问题,通常会怎么做?
用谷歌搜索引擎找到类似的问题并且你会相信(或假设)你也面临同样的问题。
你会抓住JVM-Xms和存在OutOfMemoryError异常这几个关键字的例子,然后希望通过这样的案例来快速解决客户端问题。
最后你会在你环境中使用相同的调优方法。两天后,问题仍然发生(甚至更糟或者稍微好点)……
到底是哪里错了呢?
首先,没有摸清问题根源所在?对开发环境没有正确地进行深层面(规格、负载情况等)理解。网络搜索是一个非常优秀的学习方法和知识分享工具,但是你必须结合自己的实际项目,从根本上进行分析解决。
可能缺乏基本的JVM和JVM内存管理技能,阻止你把所有的点给连接起来。
今天讲的第一条技巧是帮助你理解基本的JVM原则及其与众不同的内存空间。这些知识都是相当重要的,它可以帮助你做出有效的调优策略、更加正确合理的预测将来会产生的影响、提前知道未来需要做哪些调优工作。下面来看一下JVM参考指南:
JVM内存分为3个内存空间
Java Heap:适用于所有的JVM厂商,通常用来拆分YoungGen(幼苗)和OldGen(终身享用)空间。
PermGen(永久代):适用于Sun HotSpot VM((PermGen空间在Java7或者Java8更新中将会被删除)
Native Heap(C-Heap):适用于所有的JVM厂商。
建议把下面的文章都能看一遍,最好把Sun的Java内存管理白皮书和OpenJDKS实现下载下来并仔细阅读。
Sun HotSpot VM
IBM VM
Oracle JRockit VM
Sun(Oracle)–Java memory management white paper
OpenJDK–Open-source Java implementation
正如你所看到的,JVM内存管理比使用Xmx设置最大值更为复杂。你需要查看每个角度,包括本地和PermGen需求以及从主机上查看物理内存可用性(CPU core)。
在较大的Java Heap和较小的本地Heap比赛中,32位虚拟机可能会变得相当棘手。试图在一个32位VM如2.5GB+上设置一个大型堆,根据应用程序占用和线程数量等因素会增加OutOfMemoryError这个异常抛出。64位JVM可以解决这个问题,但物理资源可用性和垃圾回收成本仍然是有限制的(成本主要集中在GC大小收集上)。最大并不表示是最好的,所以请不要假设在一个16GB的64位虚拟机上可以运行20个Java EE应用程序。
2.数据和应用程序为王:回顾静态占用需求
应用程序以及相关数据将决定Java堆空间占用需求。通过静态内存,可“预测”下面的内存需求:
确定将会有多少不同的应用程序部署到预先计划的一个单独的JVM进程上,例如有多少个ear文件、war文件、jar文件等。在一个JVM上部署的应用程序越多,对本机堆的需求就越多。
确定有多少个类需要在运行时加载:包括第三方API。越多的类加载器和类在运行时被加载,在HotSpot VM PermGen空间和内部JIT相关优化对象上的需求就越高。
确定数据缓存占用,如应用程序加载内部缓存数据结构(和第三方API),例如数据库中的数据缓存,从文件中读取数据等。数据缓存使用越多,Java Heap OldGen空间需求就越高。
确定允许建立的中间件线程数量。这是非常重要的,因为Java线程需要足够的本机内存,否则会抛OutOfMemoryError异常。
在JVM进程上部署的应用程序越多,对本地内存和PermGen空间的要求就越高。数据缓存并不是序列化为一个磁盘或数据库,它将从OldGen空间里面需要额外的内存。
设法对静态内存占用进行合理的评估,在真正进行数据测试之前,设置一些JVM能力起点是非常有用的。对于32位JVM,通常不推荐一个Java堆大小超过2 GB(-Xms2048m,-Xmx2048m),对于Java EE应用程序和线程来说这样将需要足够的内存和本机堆PermGen。
这个评估是非常重要因为太多的应用程序部署在一个32位JVM进程上很容易导致本机堆耗尽;尤其是在多重线程环境。
对于64位JVM, 一个3GB或者4GB的Java堆/JVM进程是推荐的起点。
3.业务流量设置规则:审查动态内存占用需求
业务流量通常会决定动态内存占用。通过观察各种监控工具可以发现并发用户与请求生成的JVM GC“心跳”,这是由于频繁的创建和垃圾回收短期或者长期对象。
一个典型的32位JVM,Java堆大小设置在2 GB(使用分代&并发收集器)通常为500 MB YoungGen分配空间和1.5 GB的OldGen空间。
最大限度地减少重大GC收集的频率是获得最佳性能的关键因素,所以在高峰的时候理解和评估需要多少内存是非常重要的。
再次声明,应用程序类型和数据将决定内存需求。购物车的应用程序类型(长期居住的对象)涉及大型和非序列化会话数据,这个通常需要大型Java堆和很多OldGen空间。无状态和XML处理(很多短命的对象)繁重的应用程序需要适当YoungGen空间,以尽量减少频率主要集合。
例如:
你有5个ear应用程序(2000多个Java类)要部署(包含中间件代码)
本地堆需求估计为1GB(必须足够大以处理线程创建等等。)PermGen空间大约是512 MB。
内部静态缓存大约500MB
在高峰时间,总预测流量是5000个并发用户
每个用户的会话数据大约500K
在高峰期间,总流量会话要求是2.5GB。
正如你所看到的一样,在如此情况下,32位JVM进程就无法满足。一个典型的解决方案是进行流量拆分,在几个JVM进程或物理主机(假设有足够的硬件和CPU core可用)上。
大多数时候,业务流量将推动内存占用。除非你需要大量的数据缓存来实现适当的性能,典型的门户应用网站(媒体)繁重的应用程序需求。数据缓存太多的时候应该用一个黄色的标志标注一下,最好早点去重新审视一下一些设计元素。
4.量体裁衣
这一条,你应该做到:
理解基本的JVM原则和内存空间。
对所有应用程序有深入的了解及其它们的特点(大小、类型、动态流量、无状态对象VS有状态对象、内部内存缓存等)。
对预测业务流量(并发用户)给每一个应用程序能提出很好的观点—如果你需要一个64位的虚拟内存,那么将设置哪个作为开始。
如果需要多个JVM(中间件)过程。
等一下,这样做并不足够。虽然上面的信息是至关重要的,并且关于Java堆的设置进行了“最佳猜测”,对应用程序的行为进行模拟并且进行适当的分析、负载和性能测试来验证Java堆内存要求。
推荐Jprofiler工具给大家,学习如何使用一个分析器的最好方法是正确理解应用程序的内存占用。另一个方法是使用Eclipse MAT工具根据现有的环境进行堆转储分析。堆转储非常强大,它可以允许你查看和理解Java堆的整个内存占用,包含类加载器相关数据和在内存占用分析中必须要做的,特别是内存泄漏。
Java分析器和堆转储分析工具允许你理解和验证应用程序内存足迹,包含内存泄漏的检测和解决方案。负载测试和性能测试是必不可少的,通过模拟并发用户来验证早期评估是否正确,它也会把应用程序瓶颈暴露出来并且允许你进行微调。推荐一个非常容易上手的工具:Apache Jmeter。
最后将看一下这样的情况,应用程序在Java EE环境非常正常,直到有一天完全正常的设备启动失败,例如硬件问题。突然的环境运行能力下降和整体环境下降,到底发生了什么?
引起“多米诺效应”的原因有很多,但缺少JVM调优和处理故障转移的能力(短期额外负荷)是很常见的。如果JVM进程运行在80% + OldGen空间容量和频繁的垃圾收集,你如何预期故障转移场景?
前面模拟的负载和性能测试应该模拟这样的场景,调整你的调优设置使您的Java堆有足够的缓冲来处理额外的负载(额外的对象)在短期内。这主要适用于动态内存占用,由于故障转移意味着将重定向一些固定的并发用户给可利用的JVM进程(中间件实例)。
5.分而治之
这一条的前提是你已经完成了几十个负载测试。JVM已经不存在泄露,你的应用程序内存不能再进行任何减少。你已经尝试了几个调优策略,例如使用一个64位的Java堆空间在10GB以上。多个GC策略,尽管这样,仍然没有找到合适的可以接受的性能水平?
与当前的JVM规范相比,适当的垂直和水平伸缩,包括在每个物理主机和跨多个主机上建立JVM进程来满足整个吞吐量和容量。如果在几个逻辑仓、自身的JVM进程、线程和调优值里打破应用程序列表那么IT环境的容错能力将更强大。
“分而治之”策略包括拆分应用程序流量到多个JVM进程,下面提供一些拆分技巧:
减少每个JVM进程的Java堆大小(静态和动态的占用)
降低JVM调优复杂度。
减少GC流失和暂停每个JVM进程
增加冗余和故障切换功能
排列最新的Cloud和IT虚拟化战略
当你发现已经花费了大量的时间在64位JVM进程调优上,是时候该好好审视一下你的中间件和JVM部署策略并且利用垂直和水平缩放。这条策略的实现需要更多的硬件支持,但是从长远角度来看,是非常有效和有益的。(张红月/编译)
分享到:
相关推荐
以下是五个关键技巧,可以帮助你有效地调整Java堆内存设置: 1. **理解默认值与自定义配置**:每个JVM都有默认的堆内存大小,但这个默认值可能并不适合所有应用。你可以通过JVM启动参数-Xms和-Xmx来分别设置初始堆...
### 优化Java堆大小的五个技巧 #### 一、深入理解JVM内存管理基础 **背景与重要性:** 在优化Java堆大小之前,我们首先需要深入理解JVM内存管理的基础知识。对于初学者而言,Java堆调优和故障排除是一项极具挑战性...
以下五个技巧旨在帮助开发者更好地理解和优化Java堆大小,确保系统的稳定运行。 1. **理解JVM内存结构**: Java虚拟机(JVM)内存分为三个主要部分:Java堆、永久代( PermGen,Java 7及以后版本已移除)、本地堆...
总结,Java性能优化是一个全方位的过程,需要从代码编写、框架选择、资源配置等多个层面考虑。通过深入理解这些知识点并应用于实践中,可以显著提升Java应用程序的运行效率。在实际工作中,应结合具体项目需求和资源...
Java优化编程是提升Java应用程序性能的关键技术,涵盖了多个方面的知识,包括代码优化、内存管理、垃圾回收、并发处理以及JVM参数调优等。以下是对这些主题的详细讲解: 1. **代码优化**:编写高效的Java代码是优化...
### Java代码性能优化23种技巧详解 #### 一、避免在循环条件中使用复杂表达式 在Java中,尤其是在不进行编译器优化的情况下,循环条件会在每次迭代时都被重新计算。如果循环条件涉及复杂的表达式或者动态计算,这...
5. **Java开发经验与技巧**: - 了解和分析应用的内存需求,合理设置`-XX:MaxDirectMemorySize`。 - 使用内存分析工具监控堆内外存使用情况,及时发现潜在问题。 - 使用池化技术管理直接内存,避免频繁分配和释放...
Java 代码性能优化技巧总结 Java 代码性能优化是每个 Java 开发者都需要掌握的重要技能。以下是对 Java 代码性能优化的各种技巧的总结: 字符串池的实现 在 Java 中,字符串池(String Intern Pool)是通过使用...
【描述】"JVM优化经验总结Java开发Java经验技巧共15页.pdf.zip" 描述的是这份资料的格式,它是一个PDF文档,被压缩在一个ZIP文件中。通常这样的文档会包含一系列实践经验和策略,旨在帮助开发者提升Java应用在JVM上...
### Java中堆内存与栈内存分配浅析 #### 一、引言 在Java编程语言中,内存管理是一项至关重要的技术。程序运行时所使用的内存主要分为两类:堆内存(Heap Memory)和栈内存(Stack Memory)。理解这两种内存类型的...
《大话JAVA性能优化》这份文档深入探讨了Java程序在多个层面的性能调优策略,旨在帮助开发者提高程序运行效率,降低资源消耗,提升用户体验。以下是对这些知识点的详细阐述: 1. **代码层次优化**:在代码编写阶段...
理解这些参数的意义和如何根据应用需求调整,对于优化Java应用性能至关重要。 最后,代码质量和架构设计也对性能有直接影响。遵循SOLID原则,编写可测试和可扩展的代码,使用模块化和面向服务的设计,能够提高代码...
本教程将深入探讨如何通过各种技术和策略来优化Java程序,确保其高效运行。 首先,理解Java性能的基础是JVM(Java虚拟机)。JVM的内存管理包括堆内存、栈内存和方法区,其中垃圾回收机制对性能影响显著。优化时应...
Java性能优化是IT行业中至关重要的一个领域,尤其是在大型企业级应用和互联网服务中,高效的Java代码能够显著提升系统运行效率,降低服务器资源消耗。以下是对这四本经典书籍中的核心知识点的详细介绍: 1. **...
以上提到的Java代码优化技巧,涵盖了从变量声明、数据结构选择到资源管理等多个方面。开发者应根据具体的应用场景灵活运用这些策略,以达到提升程序性能、降低资源消耗的目的。通过持续学习和实践,逐步掌握这些技巧...