Java性能权威指南-总结5

news2024/11/24 9:14:16

Java性能权威指南-总结5

  • 垃圾收集入门
    • 垃圾收集概述
      • 分代垃圾收集器

垃圾收集入门

很多时候没有机会重写代码,又面临需要提高Java应用性能的压力,这种情况下对垃圾收集器的调优就变得至关重要。

现代JVM的类型繁多,最主流的四个垃圾收集器分别是:Serial收集器(常用于单CPU环境)、Throughput(或者Parallel)收集器、Concurrent收集器(CMS)和G1收集器。虽然它们的性能特征迥异,不过它们也有不少共性。

垃圾收集概述

Java特性之一是不需要显式地管理对象的生命周期:可以在需要时创建对象,对象不再被使用时,会由JVM在后台自动进行回收。这其实是一把双刃剑,如果花费大量的时间来优化Java程序的内存使用,这套机制看起来可能更像个缺点,而非其优点。

简单来说,垃圾收集由两步构成:查找不再使用的对象,以及释放这些对象所管理的内存。JVM从查找不再使用的对象(垃圾对象)入手。有时,这也被称为查找不再有任何对象引用的对象(暗指采用“引用计数”的方式统计对象引用)。然而,这种靠引用计数的方式不太靠谱:假设有一个对象链接列表,列表中的每一个对象(除了头节点)都指向列表中的另一个对象,但是,如果没有任何一个引用指向列表头,这个列表就没人使用,可以被垃圾回收器回收。如果这是一个循环列表(即列表的尾元素反过来又指向了头元素),那么列表中的每一个元素都包含一个引用,即使这个列表内没有任何一个对象实际被使用,因为没有任何一个对象指向这个列表。

所以引用是无法通过计数的方式动态跟踪的;JVM必须定期地扫描堆来查找不再使用的对象。一旦发现垃圾对象,JVM会回收这些对象所持有的内存,把它们分配给需要内存的其他对象。然而,简单地记录空闲内存也无法保证将来有足够的可用内存,有些时候,还必须进行内存整理来防止内存碎片。

假设以下场景,一个程序需要分配大小为1000字节的数组,紧接着又分配一个大小为24字节的数组,并在一个循环中持续进行这样的分配。最终程序会耗尽整个堆,结果如下图所示:
在这里插入图片描述
堆内存用尽会触发JVM回收不再使用的数组空间。假设所有大小为24字节的数组都不再被使用,而大小为1000字节的数组还继续使用,这就形成了上图中第二行的场景。虽然堆内部有足够的空闲空间,却找不到任何一个大于24字节的连续空间,除非JVM移动所有大小为1000字节的数组,让它们连续存储,把空闲的空间整合成一块更大的连续空间,供其他的内存分配使用(如上图的第三行)。

深入到这些具体实现似乎太过于琐屑,但垃圾收集的性能就是由这些基本操作所决定的:**找到不再使用的对象、回收它们使用的内存、对堆的内存布局进行压缩整理。**完成这些操作时不同的收集器采用了不同的方法,这也是不同垃圾收集器表现出不同性能特征的原因。

如果垃圾收集进行时,没有任何应用程序线程在运行,那么完成这些操作将是一件轻松愉快的事情。然而实际情况要复杂得多,通常Java程序都启动了大量的线程,垃圾收集器自身往往也是多线程的。接下来的讨论中,从逻辑上将线程分成两组,分别是应用程序线程和处理垃圾收集的线程。垃圾收集器回收对象,或者在内存中移动对象时,必须确保应用程序线程不再继续使用这些对象。 这一点在收集器移动对象时尤其重要:在操作过程中,对象的内存地址会发生变化,因此这个过程中任何应用线程都不应再访问该对象。

所有应用线程都停止运行所产生的停顿被称为时空停顿(stop-the-world)。通常这些停顿对应用的性能影响最大,调优垃圾收集时,尽量减少这种停顿是最为关键的考量因素。

分代垃圾收集器

虽然实现的细节千差万别,但所有的垃圾收集器都遵循了同一个方式,即根据情况将堆划分成不同的代(Generation)。这些代被称为“老年代”(Old Generation或TenuredGeneration)和“新生代”(Young Generation)。新生代又被进一步地划分为不同的区段,分别称为Eden空间和Survivor空间(不过Eden有时会被错误地用于指代整个新生代)。

采用分代机制的原因是很多对象的生存时间非常短。譬如下面这个例子,这是一个计算股价的循环,它将股价与股票均价的差值进行乘方运算,然后将结果加和(作为标准偏差计算的一部分)。

	sum = new BigDecimal(0);
	for (StockPrice sp: prices.values()) {
		BigDecimal diff = sp.getClosingPrice().subtract(averagePrice);
		diff = diff.multiply(diff);
		sum = sum.add(diff);
	}

BigDecimal同许多Java类一样是不可变对象:该对象代表的是一个不能修改的数字。运算使用这个对象时,会创建一个新的对象(通常,前一个对象及其值会被丢弃)。这个简单的循环处理一年的股票数据(大约250个循环)时,为了存储循环的中间值,会创建750个BigDecimal对象,只是在这一个循环里。这些对象在循环的下一个周期开始时会被丢弃。在add()以及其他方法内,JDK的库方法会创建更多类似BigDecimal(以及其他的类)的中间对象。最终,在一小段代码中大量的对象被快速地创建和丢弃。

Java中,这种操作是非常普遍的,所以垃圾收集器设计时就特别考虑要处理大量(有时候是大多数)的临时对象。这也是分代设计的初衷之一。新生代是堆的一部分,对象首先在新生代中分配。新生代填满时,垃圾收集器会暂停所有的应用线程,回收新生代空间。不再使用的对象会被回收,仍然在使用的对象会被移动到其他地方。这种操作被称为Minor GC。

采用这种设计有两个性能上的优势。其一,由于新生代仅是堆的一部分,与处理整个堆相比,处理新生代的速度更快。而这意味着应用线程停顿的时间会更短。这意味着应用程序线程会更频繁地发生停顿,因为JVM不再等到整个堆都填满才进行垃圾收集。然而,就目前而言,更短的停顿显然能带来更多的优势,即使发生的频率更高。

第二个优势源于新生代中对象分配的方式。对象分配于Eden空间(占据了新生代空间的绝大多数)。**垃圾收集时,新生代空间被清空,Eden空间中的对象要么被移走,要么被回收;所有的存活对象要么被移动到另一个Survivor空间,要么被移动到老年代。由于所有的对象都被移走,相当于新生代空间在垃圾收集时自动地进行了一次压缩整理。**所有的垃圾收集算法在对新生代进行垃圾回收时都存在“时空停顿”现象。

对象不断地被移动到老年代,最终老年代也会被填满,JVM需要找出老年代中不再使用的对象,并对它们进行回收。这便是垃圾收集算法差异最大的地方。简单的垃圾收集算法直接停掉所有的应用线程,找出不再使用的对象,对其进行回收,接着对堆空间进行整理。 这个过程被称为FulI GC,通常导致应用程序线程长时间的停顿。

另一方面,通过更复杂的计算,还有可能在应用线程运行的同时找出不再使用的对象;CMS和G1收集器就是通过这种方式进行垃圾收集的。由于不需要停止应用线程就能找出不再用的对象,CMS和G1收集器被称为Concurrent垃圾收集器。同时,由于它们将停止应用程序的可能降到了最小,也被称为低停顿(Low-Pause)收集器(有时也称为无停顿收集器,虽然这个叫法相当不确切)。Concurrent收集器也使用各种不同的方法对老年代空间进行压缩。

使用CMS或G1收集器时,应用程序经历的停顿会更少(也更短)。其代价是应用程序会消耗更多的CPU。 CMS和G1收集也可能遭遇长时间的Full GC停顿(尽量避免发生那样的停顿是这些调优算法要考虑的重要方面)。

评估垃圾收集器时,想想需要达到的整体性能目标。每一个决定都需要权衡取舍。如果应用对单个请求的响应时间有要求(譬如Java企业版服务器),应该考虑下面这些因素:

  • 单个请求会受停顿时间的影响——不过其受Full GC长时间停顿的影响更大。如果目标是要尽可能地缩短响应时间,那么选择使用Concurrent收集器更合适。
  • 如果平均响应时间比最大响应时间更重要(譬如90%的响应时间),采用Throughput收集器通常就能满足要求。

使用Concurrent收集器来避免长的停顿时间也有其代价,这会消耗额外的CPU。类似地,为批量应用选择垃圾收集器可以遵循下面的原则:

  • 如果CPU足够强劲,使用Concurrent收集器避免发生Full GC停顿可以让任务运行得更快。
  • 如果CPU有限,那么Concurrent收集器额外的CPU消耗会让批量任务消耗更多的时间。

快速小结

  1. 所有的GC算法都将堆划分成了老年代和新生代。
  2. 所有的GC算法在清理新生代对象时,都使用了“时空停顿”(stop-the-world)方式的垃圾收集方法,通常这是一个能较快完成的操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/610157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用RP2040自制的树莓派pico—— [2/100] HelloWorld! 和 点亮LED

使用RP2040自制的树莓派pico—— [2/100] HelloWorld! 和 点亮LED 开发环境HelloWorld!闪烁 LED 灯代码 由于比较简单就放在一起写了 开发环境 软件:Thonny HelloWorld! 要想使串口打印HelloWorld! 只需要一行代码 print("HelloWorld!")保…

c++与c中多组输入的使用

我们现在看看c中多组输入的使用 int main() {int a;//1while (~scanf("%d", &a)){}//2while (scanf("%d", &a) ! EOF){}return 0; } 这两个是等同的 我们需要知道的是scanf的返回值是成功读取的个数,我们来验证一下 我们可以看到&am…

chatgpt赋能python:Python在Mac上的运行方法

Python在Mac上的运行方法 如果你是一名使用Mac系统的Python开发人员,你肯定希望能够尽可能方便地运行Python。幸运的是,Mac系统已经预先安装了Python,但是你可能需要对其进行配置,以便更好地管理Python模块和环境。 检查Python版…

chatgpt赋能python:Python地区分析:如何使用Python进行地理数据分析

Python地区分析:如何使用Python进行地理数据分析 简介 Python是一种广泛使用的编程语言,它提供了许多强大的工具来处理大量数据。其中包括地理数据,地理数据是指地球表面的空间信息。Python中有一些强大的地图库,包括Folium和Ba…

chatgpt赋能python:Python的均值计算公式

Python的均值计算公式 在数据分析和机器学习方面,计算均值是非常常见的操作。Python提供了一些内置函数和库来计算均值。本文将介绍Python中常用的均值计算公式。 1. 算术均值 算术均值(Arithmetic Mean)是最常见的均值计算方法。Python中…

解决高并发

目录 1.4 对比单体系统、分布式系统和微服务系统 1.4.1 单体系统之痛 1、什么是单体系统 2、单体系统面临的问题 1.4.2 高并发系统之分布式架构 1.4.3 高并发系统之微服务架构 1.4 对比单体系统、分布式系统和微服务系统 接下来从企业真实场景出发,对比单体系统…

ROS:服务端Server的编程实现

目录 一、服务模型二、创建功能包三、创建代码并编译运行(C)3.1步骤3.2创建服务端Server代码3.3编译3.4运行 一、服务模型 Server端本身是进行模拟海龟运动的命令端,它的实现是通过给海龟发送速度(Twist)的指令&#x…

【Android Framework系列】第1章 Handler消息传递机制

1 Handler简介 Handler是一套Android的消息传递机制,Handler主要用于同进程的线程间通信。而Binder/Socket用于进程间通信。 2 Handler运行机制 Handler运行主要涉及到四个类:Handler、Looper、Message、MessageQueue Handler:消息处理器&…

chatgpt赋能python:Python文件备份的重要性和应用

Python文件备份的重要性和应用 在现代企业和个人用户中,数据备份是一项至关重要的工作,以防止数据丢失或损坏。当涉及到计算机数据时,文件备份是一项基本需求。文件备份还可以用于保护文件,以防它们被病毒、恶意软件或未经授权的…

法规标准-UN R158标准解读

UN R158是做什么的? UN R158全名为针对驾驶员识别车辆后方弱势道路使用者,联合国对倒车系统和机动车的统一规定,该法规涉及批准倒车和机动车辆的装置,主要为保证倒车时避免碰撞,方便驾驶员观察了解车辆后部人员和物体…

dSPACE一览(暂存)

1. SCALEXIO - dSPACE 2. dSPACE仿真流程介绍(dSPACE软件介绍、仿真演示、自动化API接口使用)_云溪溪儿的博客-CSDN博客 目录 硬件 板卡 软件 VEOS 应用领域 全面总线仿真 高效集成多种建模方法 硬件 板卡 SCALEXIO FPGA Subsystems DS6601 FPG…

STM32通过esp8266连接WiFi接入MQTT服务器

上文我们讲到如何搭建本地MQTT服务器,现在介绍如何通过stm32连接MQTT 一.首先我们初始化esp8266这里我们使用的是USART4与其通信代码如下 void UART4_Init(uint32_t bound) {GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;RCC_APB1…

高通 Camera HAL3:添加一条PipeLine

一.概述 添加一条PipeLine实现两路Raw进,一路Raw出 二.简介 要添加的PipeLine:SWMFMergeRawTwo2One 包含1个memcpy node,这个node用于将2个raw buffer input输入 变为 1个raw buffer output输出 三.添加 3.1 在相应的Usecase下添加一个p…

Spring Cloud Alibaba - Nacos源码分析(一)

目录 一、源码 1、为什么要分析源码 2、看源码的方法 二、Nacos服务注册与发现源码剖析 1、Nacos核心功能点 2、Nacos服务端/客户端原理 2.1、nacos-example 2.2、Nacos-Client测试类 3、项目中实例客户端注册 一、源码 1、为什么要分析源码 1. 提升技术功底&#x…

NB使用MQTT连接格物平台

内容简介: 本文主要记述了怎么使用NB-IoT模块,采用MQTT协议连接联通的格物平台,并且实现单属性和多属性数据的上报。 1 创建产品 打开格物平台,进行注册登录;之后点击页面的控制台,进入设备管理引擎&#x…

爬取中文新闻+正向、逆向最大匹配算法分词+算法优化+P、R、F值评估(完整详细过程+Python源码)

如标题所示,本文旨在记录这次分词实验,将主要从以下四点展开: 1、新闻文本的获取(完整爬虫过程) 爬取新闻网站中多个板块的新闻,这里建议可以多爬一些板块,保证新闻内容主题丰富,分…

计算机网络 - 移动网络

Wireless links and network Characterstics 无线网络信号会衰减很快, 会被别的source影响, 传播过程中经历多个路径比如经过建筑, 树木等收到影响. 所以就有了信噪比的概念 CDMA CDMA是一种在多点连接时候避免collision的一种算法. 主要是每个sender都用不同的编码, 然后se…

chatgpt赋能python:Python声音处理入门指南

Python声音处理入门指南 如果你是一个音乐爱好者或者处理声音的工程师,Python语言是值得你考虑的一种工具,它拥有丰富的库,可以帮助你在声音分析、编辑、压缩和转换等方面做出成果。 Python声音处理库 Python语言拥有一个大量的声音处理库…

电脑通过VNC连接树莓派

0. 实验准备 VNC软件 VNC Viewer 或者 MobaXterm(安装包点击即可下载) 可以使用SSH登录进去或者有屏幕的树莓派 一台可以使用的电脑 树莓派和电脑连接在同一个局域网下 0.5 树莓派的公共操作 打开树莓派的 VNC 功能 有屏幕的 打开 VNC 功能&#xff…

日志框架 --- Logback

文章目录 1. 什么是logback2. logback的日志级别3. 日志级别的层级4. logback配置文件4.1 logger标签4.2 root标签4.3 appender标签4.4 filter标签4.5 encoder标签 5. 整体演示5.1 配置文件5.2 运行结果 1. 什么是logback Logback是一个用于Java应用程序的日志框架&#xff0c…