JVM概述

整理文档很辛苦,赏杯茶钱您下走!

免费阅读已结束,点击下载阅读编辑剩下 ...

阅读已结束,您可以下载文档离线阅读编辑

资源描述

JVM性能优化(一)JVM技术入门作者EvaAndreasson译者:赵峰校对:方腾飞原文链接Java应用程序是运行在JVM上的,但是你对JVM技术了解吗?这篇文章(这个系列的第一部分)讲述了经典Java虚拟机是怎么样工作的,例如:Java一次编写的利弊,跨平台引擎,垃圾回收基础知识,经典的GC算法和编译优化。之后的文章会讲JVM性能优化,包括最新的JVM设计——支持当今高并发Java应用的性能和扩展。如果你是一个开发人员,你肯定遇到过这样的特殊感觉,你突然灵光一现,所有的思路连接起来了,你能以一个新的视角来回想起你以前的想法。我个人很喜欢学习新知识带来的这种感觉。我已经有过很多次这样的经历了,在我使用JVM技术工作时,特别是使用垃圾回收和JVM性能优化时。在这个新的Java世界中,我希望和你分享我的这些启发。希望你能像我写这篇文章一样兴奋的去了解JVM的性能。这个系列文章,是为所有有兴趣去学习,更多JVM底层知识和JVM实际做了什么的Java开发人员所写的。在更高层次,我将讨论垃圾回收和在不影响应用运行的情况下,对空闲内存安全和速度上的无止境追求。你将学到JVM的关键部分:垃圾回收和GC算法,编译优化,和一些常用的优化。我同样会讨论为什么Java标记会很难,并提供建议什么时候应该考虑测试性能。最后,我将讲一些JVM和GC的新的创新,包括Azul’sZingJVM,IBMJVM,和Oracle’sGarbageFirst(G1)垃圾回收中的重点。我希望你读完这个系列时对Java可扩展性的限制有更深的了解,这样限制是如何强制我们以最优的方式创建一个Java部署。希望你会有一种豁然开朗的感受,并且能激发了一些好的Java灵感:停止接受那些限制,并去改变它!如果你现在还不是一个开源工作者,这个系列或许会鼓励你往这方面发展。JVM性能调优:阅读该系列第一部分:概述第二部分:编译工具第三部分:垃圾回收第四部分:并发压缩GC第五部分:可扩展性JVM性能和“一次编译,到处运行”的挑战我有新的消息告诉那些固执的认为Java平台本质上是缓慢的人。当Java刚刚做为企业级应用的时候,JVM被诟病的Java性能问题已经是十几年前的事了,但这个结论,现在已经过时了。这是真的,如果你现在在不同的开发平台上运行简单静态和确定的任务时,你将很可能发现使用机器优化过的代码,比使用任何虚拟环境执行的要好,在相同的JVM下。但是,Java的性能在过去10年有了非常大的提升。Java产业的市场需求和增长,导致了少量的垃圾回收算法、新的编译创新、和大量的启发式方法和优化,这些使JVM技术得到了进步。我将在以后的章节中介绍这些。JVM的技术之美,同样是它最大的挑战:没有什么可以被认为是“一次编译,到处运行”的应用。不是优化一个用例,一个应用,一个特定的用户负载,JVM不断的跟踪Java应用现在在做什么,并进行相应的优化。这种动态的运行导致了一系列动态的问题。当设计创新时(至少不是在我们向生产环境要性能时),致力于JVM的开发者不会依赖静态编译和可预测的分配率。JVM性能的事业在我早期的工作中我意识到垃圾回收是非常难“解决”的,我一直着迷于JVMs和中间件技术。我对JVMs的热情开始于我在JRockit团队中时,编码一种新的方法用于自学,自己调试垃圾回收算法(参考Resources)。这个项目(转变为JRockit一个实验性的特点,并成为DeterministicGarbageCollection算法的基础)开启了我JVM技术的旅程。我已经在BEA系统、Intel、Sun和Oracle(因为Oracle收购BEA系统,所以被Oracle短暂的工作过)工作过。之后我加入了在AzulSystems的团队去管理ZingJVM,现在我为Cloudera工作。机器优化的代码可能会实现较好的性能(但这是以牺牲灵活性来做代价的),但对于动态装载和功能快速变化的企业应用,这并不是一个权衡选择它的理由。大多数的企业为了Java的优点,更愿意去牺牲机器优化代码,带来完美的性能。易于编码和功能开发(意义是更短的时间去响应市场)得到知识渊博的的程序员用JavaAPIs和标准库更快速的开发可移植性——不用为新的平台去重新写Java应用从Java代码到字节码做为一个Java程序员,你可能对编码、编译和执行Java应用很熟悉。例子:我们假设你有一个程序(MyApp.java),现在你想让它运行。去执行这个程序你需要先用javac(JDK内置的静态Java语言到字节码编译器)编译。基于Java代码,javac生成相应的可执行字节码,并保存在相同名字的class文件:MyApp.class中。在把Java代码编译成字节码后,你可以通过java命令(通过命令行或startup脚本,使用不使用startup选项都可以)来启动可执行的class文件,从而运行你的应用。这样你的class被加载到运行时(意味着Java虚拟机的运行),程序开始执行。这就是表面上每一个应用执行的场景,但是现在我们来探究下当你执行java命令时究竟发生了什么。Java虚拟机是什么?大多数开发人员通过持续调试来与JVM交互——akaselecting和value-assigning启动选项能让你的Java程序跑的更快,同时避免了臭名昭著的”outofmemory”错误。但是,你是否曾经想过,为什么我们起初需要一个JVM来运行Java应用呢?什么是Java虚拟机?简单的说,一个JVM是一个软件模块,用于执行Java应用字节码并且把字节码转化到硬件,操作系统特殊指令。通过这样做,JVM允许Java程序在第一次编写后可以在不同的环境中执行,并不需要更改原始的代码。Java的可移植性是通往企业应用语言的关键:开发者并不需要为不同平台重写应用代码,因为JVM负责翻译和平台优化。一个JVM基本上是一个虚拟的执行环境,作为一个字节码指令机器,而用于分配执行任务和执行内存操作通过与底层的交互。一个JVM同样为运行的Java应用管理动态资源。这就意味着它掌握分配和释放内存,在每个平台上保持一致的线程模型,在应用执行的地方用一种适于CPU架构的方式组织可执行的指令。JVM把开发人员从需要跟踪对象的引用和存活时长中解放出来。同样的它不用我们管理何时去释放内存——一个像C语言那样的非动态语言的痛点。你可以把JVM当做是一个专门为Java运行的操作系统;它的工作是为Java应用管理运行环境。一个JVM是一个通过与底层交互的虚拟执行环境,作为一个字节码指令机器,而用于分配执行任务和执行内存操作。JVM组件概述有很多写JVM内部和性能优化的文章。作为这个系列的基础,我将会总结概述下JVM组件。这个简短的阅览会为刚接触JVM的开发者有特殊的帮助,会让你了解之后更想深入的讨论。从一种语言到另一种——关于Java编译器编译器是输入一种语言,然后输出另一种可执行的语句。Java编译器有两个主要任务:1.让Java语言更加轻便,不用每次在特定的平台上写代码;2.确保在特定的平台产生有效的可执行的代码。编译器可以是静态也可以是动态。一个静态编译的例子是javac。它把Java代码当做输入,并转化为字节码(一种在Java虚拟机执行的语言)。静态编译器一次解释输入的代码,输出可执行的形式,这个是在程序执行时将被用到。因为输入是静态的,你将总能看到结果相同。只有当你修改原始代码并重新编译时,你才能看到不同的输出。动态编译器,例如Just-In-Time(JIT)编译器,把一种语言动态的转化为另一种,这意味着它们在运行时执行代码。一个JIT编译器让你收集或创建运行数据分析(通过插入性能计数的方式实现)andmakecompilerdecisionsonthefly,usingtheenvironmentdataathand(这一段不知道怎么翻译)。动态的编译器在编译的过程中,实现更好的指令序列,把一系列的指令替换成更有效的,并消除多余的操作。随着时间的增长你将收集更多的代码配制数据,做更多更好的编译决定;整个过程就是我们通常称为的代码优化和重编译。动态编译给了你可以根据行为进行动态调整的优势,或随着应用装载次数的增加催生的新的优化。这就是为什么动态编译器非常适合Java运行。值得注意的是,动态编译器请求外部数据结构,线程资源,CPU周期分析和优化。越深层次的优化,你将需要越多的资源。然而在大多数环境中,顶层对执行性能的提升帮助非常小——比你纯粹的解释要快5到10倍的性能。分配会导致垃圾回收每一个线程基于每个“Java进程分配内存地址空间”完成内存分配,叫Java堆,或简称堆。在Java世界中单线程分配在客户端应用程序中很常见。然而,单线程分配在企业应用和工作装载服务端变的没有任何益处,因为它并没有使用现在多核环境的并行优势。并行应用设计同样迫使JVM保证在同一时间,多线程不会分配同一个地址空间。你可以通过在整个分配空间中放把锁来控制。但这种技术(通常叫做堆锁)很消耗性能,持有或排队线程会影响资源利用和应用优化的性能。多核系统好的一面是,它们创造了一个需求,为各种各样的新的方法在资源分配的同时去阻止单线程的瓶颈,和序列化。一个常用的方法是把堆分成几部分,在对应用来说每个合式分区大小的地方——显然它们需要调优,分配率和对象大小对不同应用来说有显著的变化,同样线程的数量也不同。线程本地分配缓存(ThreadLocalAllocationBuffer,简写:TLAB),或者有时,线程本地空间(ThreadLocalArea,简写:TLA),是一个专门的分区,在其中线程不用声明一个全堆锁就可以自由分配。当区域满的时候,堆就满了,表示堆上的空闲空间不够用来放对象的,需要分配空间。当堆满的时候,垃圾回收就会开始。碎片使用TLABs捕获异常,是把堆碎片化来降低内存效率。如果一个应用在要分配对象时正巧不能增加或者不能完全分配一个TLAB空间,这将会有空间太小而不能生成新对象的风险。这样的空闲空间被当做“碎片”。如果应用程序一直保持对象的引用,然后再用剩下的空间分配,最后这些空间会在很长一段时间内空闲。碎片就是当碎片被分散在堆中的时候——通过一小段不用的内存空间来浪费堆空间。为你的应用分配“错误的”TLAB空间(关于对象的大小、混合对象的大小和引用持有率)是导致堆内碎片增多的原因。在随着应用的运行,碎片的数量会增加在堆中占有的空间。碎片导致性能下降,系统不能给新应用分配足够的线程和对象。垃圾回收器在随后会很难阻止out-of-memory异常。TLAB浪费在工作中产生。一种方法可以完全或暂时避免碎片,那就是在每次基础操作时优化TLAB空间。这种方法典型的作法是应用只要有分配行为,就需要重新调优。通过复杂的JVM算法可以实现,另一种方法是组织堆分区实现更有效的内存分配。例如,JVM可以实现free-lists,它是连接起一串特定大小的空闲内存块。一个连续的空闲内存块和另一个相同大小的连续内存块相连,这样会创建少量的链表,每个都有自己的边界。在有些情况下free-lists导致更好的合适内存分配。线程可以对象分配在一个差不多大小的块中,这样比你只依靠固定大小的TLAB,潜在的产生少的碎片。GC琐事有一些早期的垃圾收集器拥有多个老年代,但是当超过两个老年代的时候会导致开销超过价值。另一种优化分配减少碎片的方法,就是创造所谓的新生代,这是一个专门用于分配新对象的专用堆空间。剩余的堆会成为所谓的老年代。老年代是用来分配长时间存在的对象的,被假定会存在很长时间的对象包括不被垃圾收集的对象或者大对象。为了更好的理解这种分配的方法,我们需要讲一些垃圾收集的知识。垃圾回收和应用性能垃圾回收是JVM的垃圾回收器去释放没有引用的被占据的堆内存。当第一次触发垃圾收集时,所有的对象引用还被保存着,被以前的引用占据的空间被释放或重新分配。当所有可回收的内存被收集后,空间等待被抓取和再次分配给新对象。垃圾回收器永远都不能重声明一个引用对象,这样做会破坏JVM的标准规范。这个规则的异常是一个可以捕获的

1 / 6
下载文档,编辑使用

©2015-2020 m.777doc.com 三七文档.

备案号:鲁ICP备2024069028号-1 客服联系 QQ:2149211541

×
保存成功