JVM性能和“一次编译,到处运行”的挑战

我有新的消息告诉那些固执的认为Java平台本质上是缓慢的人。当Java刚刚做为企业级应用的时候,JVM被诟病的Java性能问题已经是十几年前的事了,但这个结论,现在已经过时了。这是真的,如果你现在在不同的开发平台上运行简单静态和确定的任务时,你将很可能发现使用机器优化过的代码,比使用任何虚拟环境执行的要好,在相同的JVM下。但是,Java的性能在过去10年有了非常大的提升。Java产业的市场需求和增长,导致了少量的垃圾回收算法、新的编译创新、和大量的启发式方法和优化,这些使JVM技术得到了进步。我将在以后的章节中介绍这些。

JVM的技术之美,同样是它最大的挑战:没有什么可以被认为是“一次编译,到处运行”的应用。不是优化一个用例,一个应用,一个特定的用户负载,JVM不断的跟踪Java应用现在在做什么,并进行相应的优化。这种动态的运行导致了一系列动态的问题。当设计创新时(至少不是在我们向生产环境要性能时),致力于JVM的开发者不会依赖静态编译和可预测的分配率。

JVM性能的事业

在我早期的工作中我意识到垃圾回收是非常难“解决”的,我一直着迷于JVMs和中间件技术。我对JVMs的热情开始于我在JRockit团队中时,编码一种新的方法用于自学,自己调试垃圾回收算法(参考 Resources)。这个项目(转变为JRockit一个实验性的特点,并成为Deterministic Garbage Collection算法的基础)开启了我JVM技术的旅程。我已经在BEA系统、Intel、Sun和Oracle(因为Oracle收购BEA系统,所以被Oracle短暂的工作过)工作过。之后我加入了在Azul Systems的团队去管理Zing JVM,现在我为Cloudera工作。对于新手小白想更轻松的学好Java提升,Java架构,web开发、大数据,数据分析,人工智能等技术,这里给大家分享系统教学资源,扩列下我尉(同英):CGMX9880 【教程/工具/方法/解疑】

机器优化的代码可能会实现较好的性能(但这是以牺牲灵活性来做代价的),但对于动态装载和功能快速变化的企业应用,这并不是一个权衡选择它的理由。大多数的企业为了Java的优点,更愿意去牺牲机器优化代码,带来完美的性能。

易于编码和功能开发(意义是更短的时间去响应市场)
得到知识渊博的的程序员
用Java APIs和标准库更快速的开发
可移植性——不用为新的平台去重新写Java应用
从Java代码到字节码

做为一个Java程序员,你可能对编码、编译和执行Java应用很熟悉。例子:我们假设你有一个程序(MyApp.java),现在你想让它运行。去执行这个程序你需要先用javac(JDK内置的静态Java语言到字节码编译器)编译。基于Java代码,javac生成相应的可执行字节码,并保存在相同名字的class文件:MyApp.class中。在把Java代码编译成字节码后,你可以通过java命令(通过命令行或startup脚本,使用不使用startup选项都可以)来启动可执行的class文件,从而运行你的应用。这样你的class被加载到运行时(意味着Java虚拟机的运行),程序开始执行。

这就是表面上每一个应用执行的场景,但是现在我们来探究下当你执行java命令时究竟发生了什么。Java虚拟机是什么?大多数开发人员通过持续调试来与JVM交互——aka selecting 和value-assigning启动选项能让你的Java程序跑的更快,同时避免了臭名昭著的”out of memory”错误。但是,你是否曾经想过,为什么我们起初需要一个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虚拟机执行的语言)。静态编译器一次解释输入的代码,输出可执行的形式,这个是在程序执行时将被用到。因为输入是静态的,你将总能看到结果相同。只有当你修改原始代码并重新编译时,你才能看到不同的输出。对于新手小白想更轻松的学好Java提升,Java架构,web开发、大数据,数据分析,人工智能等技术,这里给大家分享系统教学资源,扩列下我尉(同英):CGMX9880 【教程/工具/方法/解疑】

动态编译器,例如Just-In-Time (JIT)编译器,把一种语言动态的转化为另一种,这意味着它们在运行时执行代码。一个JIT编译器让你收集或创建运行数据分析(通过插入性能计数的方式实现),并且编译器使用这些环境数据快速的做出决定。动态的编译器在编译的过程中,实现更好的指令序列,把一系列的指令替换成更有效的,并消除多余的操作。随着时间的增长,你将收集更多的代码生成数据,做更多更好的编译决定;整个过程就是我们通常称为的代码优化和重编译。

动态编译给了你根据行为进行动态调整的优势,或随着应用装载次数的增加从而进行新的优化。这就是为什么动态编译器非常适合Java运行。值得注意的是,动态编译器请求外部数据结构,线程资源,CPU周期分析和优化。越深层次的优化,你将需要越多的资源。然而在大多数环境中,顶层对执行性能的提升帮助非常小——比你纯粹的解释要快5到10倍的性能。

分配会导致垃圾回收

每一个线程基于每个“Java进程分配内存地址空间” 完成内存分配,叫Java堆,或简称堆。在Java世界中单线程分配在客户端应用程序中很常见。然而,单线程分配在企业应用和工作装载服务端变的没有任何益处,因为它并没有使用现在多核环境的并行优势。

并行应用设计同样迫使JVM保证在同一时间,多线程不会分配同一个地址空间。你可以通过在整个分配空间中放把锁来控制。但这种技术(通常叫做堆锁)很消耗性能,持有或排队线程会影响资源利用和应用优化的性能。多核系统好的一面是,它们创造了一个需求,为各种各样的新的方法在资源分配的同时去阻止单线程的瓶颈和序列化。

一个常用的方法是把堆分成几部分,在对应用来说每个合适分区大小的地方——显然它们需要调优,分配率和对象大小对不同应用来说有显著的变化,同样线程的数量也不同。线程本地分配缓存(Thread Local Allocation Buffer,简写:TLAB),或者有时,线程本地空间(Thread Local Area,简写:TLA),是一个专门的分区,在其中线程不用声明一个全堆锁就可以自由分配。当区域满的时候,堆就满了,表示堆上的空闲空间不够用来放对象,需要分配空间。当堆满的时候,垃圾回收就会开始。

碎片

使用TLABs捕获异常,是把堆碎片化来降低内存效率。如果一个应用在要分配对象时,正巧不能增加或者不能完全分配一个TLAB空间,这将会有空间太小而不能生成新对象的风险。这样的空闲空间被当做“碎片”。如果应用程序一直保持对象的引用,然后再用剩下的空间分配,最后这些空间会在很长一段时间内空闲。

碎片就是当碎片被分散在堆中的时候——通过一小段不用的内存空间来浪费堆空间。为你的应用分配 “错误的”TLAB空间(关于对象的大小、混合对象的大小和引用持有率)是导致堆内碎片增多的原因。在随着应用的运行,碎片的数量会增加在堆中占有的空间。碎片导致性能下降,系统不能给新应用分配足够的线程和对象。垃圾回收器在随后会很难阻止out-of-memory异常。

TLAB浪费在工作中产生。一种方法可以完全或暂时避免碎片,那就是在每次基础操作时优化TLAB空间。这种方法典型的作法是应用只要有分配行为,就需要重新调优。通过复杂的JVM算法可以实现,另一种方法是组织堆分区实现更有效的内存分配。例如,JVM可以实现free-lists,它是连接起一串特定大小的空闲内存块。一个连续的空闲内存块和另一个相同大小的连续内存块相连,这样会创建少量的链表,每个都有自己的边界。在有些情况下free-lists导致更好的合适内存分配。线程可以对象分配在一个差不多大小的块中,这样比你只依靠固定大小的TLAB,潜在的产生少的碎片。

GC琐事

有一些早期的垃圾收集器拥有多个老年代,但是当超过两个老年代的时候会导致开销超过价值。另一种优化分配减少碎片的方法,就是创造所谓的新生代,这是一个专门用于分配新对象的专用堆空间。剩余的堆会成为所谓的老年代。老年代是用来分配长时间存在的对象的,被假定会存在很长时间的对象包括不被垃圾收集的对象或者大对象。为了更好的理解这种分配的方法,我们需要讲一些垃圾收集的知识。

垃圾回收和应用性能

垃圾回收是JVM的垃圾回收器去释放没有引用的被占据的堆内存。当第一次触发垃圾收集时,所有的对象引用还被保存着,被以前的引用占据的空间被释放或重新分配。当所有可回收的内存被收集后,空间等待被抓取和再次分配给新对象。

垃圾回收器永远都不能重声明一个引用对象,这样做会破坏JVM的标准规范。这个规则的异常是一个可以捕获的soft或weak引用 ,如果垃圾收集器将要将近耗尽内存。我强烈推荐你尽量避免weak引用,然而,因为Java规范的模糊导致了错误的解释和使用的错误。更何况,Java是被设计为动态内存管理,因为你不需要考虑什么时候和什么地方释放内存。

垃圾收集器的一个挑战是在分配内存时,需要尽量不影响运行着的应用。如果你不尽量垃圾收集,你的应用将耗近内存;如果你收集的太频繁,你将损失吞吐量和响应时间,这将对运行的应用产生坏的影响。

GC算法

有许多不同的垃圾回收算法。稍后,在这个系列里将深入讨论几种。在最高层,垃圾收集最主要的两个方法是引用计数和跟踪收集器。

引用计数收集器会跟踪一个对象指向多少个引用。当一个对象的引用为0时,内存将被立即回收,这是这种方法的优点之一。引用计数方法的难点在于环形数据结构和保持所有的引用即时更新。

跟踪收集器对仍在引用的对象标记,用已经标记的对象,反复的跟随和标记所有的引用对象。当所有的仍然引用的对象被标记为“live”时,所有的不被标记的空间将被回收。这种方法用于管理环形数据结构,但是在很多情况下收集器在回收不被引用的内存之前,需要等待所有标记完成。

有不种的算法来实现上面的方法。最著名的算法是 marking 或copying 算法, parallel 或 concurrent算法。我将在稍后的文章中讨论这些。

通常来说垃圾回收的意义是,致力于在堆中给新对象和老对象分配地址空间。其中“老对象”是指在许多垃圾回收后幸存的对象。用新生代来存放新对象,老年代存放老对象,这样能通过快速回收短时间占据内存的对象来减少碎片,同样通过把长时间存在的对象聚合在一起,并把它们放到老年代地址空间中。所有这些在长时间对象和保存堆内存不碎片化之间减少了碎片。新生代的一个积极作用是延迟了需要花费更大代价回收老年代对象的时间,你可以为短暂的对象重复利用相同的空间。(老空间的收集会花费更多,是因为长时间存在的对象们,会包含更多的引用,需要更多的遍历。)

最后值的一提的算法是compaction,这是管理内存碎片的方法。Compaction基本来说就是把对象移动到一起,从来释放更大的连续内存空间。如果你熟悉磁盘碎片和处理它的工具,你会发现compaction跟它很像,不同的是这个运行在Java堆内存中。我将在系列中详细讨论compaction。

总结:回顾和重点

JVM允许可移植(一次编程,到处运行)和动态的内存管理,所有Java平台的主要特性,都是它受欢迎和提高生产力的原因。

在第一篇JVM性能优化系统的文章中我解释了一个编译器怎么把字节码转化为目标平台的指令语言的,并帮助动态的优化Java程序的执行。不同的应用需要不同的编译器。

我同样简述了内存分配和垃圾收集,和这些怎么与Java应用性能相关的。基本上,你越快的填满堆和频繁的触发垃圾收集,Java应用的占有率越高。垃圾收集器的一个挑战是在分配内存时,在应用耗尽内存之前回收内存,但是尽量不影响运行着的应用。在以后的文章中我们会更详细的讨论传统的和新的垃圾回收和JVM性能优化。

【Java架构师】JVM性能优化(一)JVM技术入门下相关推荐

  1. eclipse lombok插件安装_如果你是Java架构师或项目经理,项目技术会允许使用Lombok吗?

    Lombok Lombok项目是一个Java库,通过注解,来消除Java类中的大量样板代码.比如常见的Getter&Setter.toString().构造函数等等. 看个例子,以前我们构建一 ...

  2. Java架构师面试网整理-JVM面试专题(共8题含答案)

    Java架构师面试网总结了JVM一些经典面试题,分享出大致的解题思路,希望对大家有帮助,有哪里你觉得不正确的话,欢迎指出,后续有空会更新. 所有面试题均由小编从各个渠道收集整理,整理不易,点个关注吧, ...

  3. 一篇文章搞懂Java架构师的核心技能及薪资!

    Java架构师一般的薪资是多少?高不高?一般来讲,Java架构师是一个比较全面的职位,它既需要学习Java开发工具.性能优化.源码分析.分布式架构.微服务架构和多线程并发编程等基础技术,又需要有组织能 ...

  4. java架构师主要负责什么_Java架构师主要学什么 Java架构师工资多少

    Java架构师主要学什么 Java架构师工资多少 Java架构师是当下一个热门的职业,这已成不争的事实.学习Java架构技术成目前较为流行的一种趋势,作为Java架构师主要学什么呢?Java架构师工资 ...

  5. java架构师年薪_Java架构师一般的薪资是多少?高不高?

    Java架构师一般的薪资是多少?高不高?一般来讲,Java架构师是一个比较全面的职位,它既需要学习Java开发工具.性能优化.源码分析.分布式架构.微服务架构和多线程并发编程等基础技术,又需要有组织能 ...

  6. Java架构师成长之路

    目录导航 前言 一.源码分析专题 1.1 设计模式详解 1.2 Mybatis源码分析 1.3 Spring5源码分析 二.分布式架构专题 2.1 漫谈分布式架构 2.2 分布式架构的基础 2.3 分 ...

  7. 史上最强Java架构师的13大技术能力讲解! | 附架构师能力图谱

    从程序员进阶成为架构师,并非一蹴而就,需要系统化.阶段性地学习,在实战项目中融会贯通,这如同打怪通关,我们得一关一关突破,每攻破一个关口,就能得到更精良的装备,技能值也随之不断增长,直至大获全胜. 凡 ...

  8. java架构师学历要求_java架构师多少薪资?如何成为java架构师大神?

    对于程序员来说,很多人想成为java架构师,毕竟成为架构师的话,工作有前景,薪资待遇也是非常高的,那么java架构师到底有多少薪资?接下来,我们就来给大家讲解一下这方面的内容. 某职业网站最新数据统计 ...

  9. java架构师什么学校好_Java架构师之路:年薪八十万的架构师课程

    原标题:Java架构师之路:年薪八十万的架构师课程 不管是开发.测试.运维,每个技术人员心里都有一个成为技术大牛的梦,毕竟"梦想总是要有的,万一实现了呢"!正是对技术梦的追求,促使 ...

  10. 阿里P8架构师谈:java架构师面试技能24全点

    1,JAVA基础扎实,理解io.多线程.集合等基础框架,对JVM原理有一定的了解,熟悉常见类库,常见java api不仅会用更能知其所以然: 2,对Spring,MyBatis/Hibernate,S ...

最新文章

  1. Ubuntu终端远程工具
  2. 程序员缺乏经验的 7 种表现!
  3. 小记mysql备份同库中一张表的历史记录
  4. spring 使用redis集群配置
  5. bmp调色板颜色信息重复_PASCAL VOC数据集-分割标签索引颜色对照及程序
  6. Moodle的qq登录版块的使用
  7. [CHM]果壳中的XAML(XAML in a Nutshell)
  8. linux系统管理Linux系统实验,实验4-Linux系统管理实验.pdf
  9. GCC 编译 --sysroot
  10. SpringBoot 注入的@service为空,运行时报空指针
  11. 中国焦磷酸四钾市场趋势报告、技术动态创新及市场预测
  12. lesson 4 Show Messages in Messagebox
  13. node版本管理和npm源管理工具
  14. 安装运行jupyter notebook时报错:ModuleNotFoundError: No module named 'prompt_toolkit.formatted_text'...
  15. win2008R2 像CA证书服务器(Linux)申请CA证书
  16. python小游戏1:大鱼吃小鱼
  17. Jenkins拉取代码返回错误码128
  18. 利用Windows系统自带命令手工搞定病毒
  19. 破壳漏洞的原理与利用
  20. 毕业三年,初心你忘记了吗?

热门文章

  1. 【入门2】分支结构 (今天刷洛谷了嘛)
  2. c语言周林答案,C语言程序设计实训教程教学课件作者周林ch04结构化程序设计课件.ppt...
  3. linux 驱动日志,Linux上的自由空间驱动的日志轮换?
  4. php swoole hyperf,【php】Hyperf为什么要关闭Swoole协程短名称
  5. 调制的缺点_DML、EAM与MZI调制的比较
  6. python 去重_上来就情感分析?我还是先用python去去重吧!
  7. 用计算机探索规律的ppt,用计算机探索规律.ppt
  8. 影响线型缩聚物分子量的因素_高分子化学试题
  9. strstr函数_leetcode第28题实现strStr()
  10. 怎么创建PHP函数,如何创建 PHP 函数