【Java应用服务体系】「序章入门」全方位盘点和总结调优技术专题指南

简介: 【Java应用服务体系】「序章入门」全方位盘点和总结调优技术专题指南

专题⽬标

本系列专题的目标是希望可以帮助读者们系统和全访问掌握应⽤系统调优的思路与方案以及相关的调优工具的使用,虽然未必会覆盖目前的所有的问题场景,但是还是提供了较为丰富的案例和调优理论,会帮助大家打开思维去⽀撑系统服务体系优化能力。

适合人员

Java相关的开发人员、系统架构师、数据库DB人员以及运维人员等

什么是调优

调优手段就是让计算机的硬件或软件在正常地⼯作基础上,非常出色的发挥其应有的性能,并且将所承担的负担降低到最低的技术手段。在Java应用服务体系中有大致可以分为5个维度的调优方向。

调优技术的五个维度

  • 应⽤⾃身的调优
  • 运⾏环境的调优(JVM的调优)
  • 存储上的调优(数据库的调优)
  • 操作系统的调优
  • 架构上的调优

如下图所示。



一般从上到下系统优化的层面成本越来越高,而从下到上系统优化层面的成本越来月底,而且难度也适当下降,建议自下而上的去进行调优规划。

调优技术的四条准则

借助监控预防问题、发现问题,监控 + 告警

采用监控和预防的手段去实现提前发现问题:zabbix、promethus等等

借助⼯具定位问题

问题排查工具使用机制

定期复盘,防⽌同类问题再现

定期进行排查和复盘相关的代码问题,加深我们对问题的印象以及防止问题再次发生

定好规范,⼀定程度上规避问题

制定标准规范,约束问题的发生。

调优的原则

有问题,解决问题。not broken, don't fix.

应⽤调优

应⽤调优-⼯具篇

  • ⼯具旨在帮助我们快速找到应⽤的性能瓶颈。
  • ⽇志分析⼯具⽐较与分析
  • ELK、GrayLog、SLSLog...
  • ELK搭建与使⽤
  • 现场演示
  • 调⽤链跟踪⼯具与对⽐
  • Skywalking、Sleuth + Zipkin、Jaeger...
  • Skywalking快速发现性能瓶颈

应⽤调优常⽤技巧-池化技术-对象池

通过复⽤对象,减少对象创建、垃圾回收的开销

适⽤场景

维护⼀些很⼤、创建很慢的对象,提升性能 缺点:有学习成本、增加了代码的复杂度

对象池框架

Apache Commons-Pool2
Commons-Pool2详解

两⼤类对象池:ObjectPool & KeyedObjectPool

ObjectPool

实现类如下,其中,最重要、功能最强、使⽤最⼴泛的GenericObjectPool,这个对象池⾮常的强⼤,它⽐较的通⽤,⽽且封装得也⾮常完备。

  • BaseObjectPool:抽象类,⽤来扩展⾃⼰的对象池
  • ErodingObjectPool:“腐蚀”对象池,代理⼀个对象池,并基于factor参数,为其添加“腐蚀”⾏为。归还的对象被腐蚀后,将会丢弃,⽽不是添加到空闲容量中。
  • GenericObjectPool:⼀个可配置的通⽤对象池实现。
  • ProxiedObjectPool:代理⼀个其他的对象池,并基于动态代理(⽀持JDK代理和CGLib代理),返回⼀个代理后的对象。该对象池主要⽤来增强对池化对象的控制,⽐如防⽌在归还该对象后,还继续使⽤该对象等。
  • SoftReferenceObjectPool:基于软引⽤的对象池
  • SynchronizedObjectPool:代理⼀个其他对象池,并为其提供线程安全的能⼒。
核⼼API如下
  • borrowObject() 从对象池中借对象
  • returnObject() 将对象归还到对象池
  • invalidateObject() 失效⼀个对象
  • addObject() 增加⼀个空闲对象,该⽅法适⽤于使⽤空闲对象预加载对象池
  • clear() 清空空闲的所有对象,并释放相关资源
  • close() 关闭对象池,并释放相关资源
  • getNumIdle() 获得空闲的对象数量
  • getNumActive() 获得被借出对象数量
KeyedObjectPool

这种对象池和ObjectPool的区别在于,它是通过key找对象的,从设计上来看和ObjectPool没什么区别。实现类如下,使⽤最⼴的是GenericKeyedObjectPool。

  • ErodingKeyedObjectPool 类似ErodingObjectPool
  • GenericKeyedObjectPool 类似GenericObjectPool
  • ProxiedKeyedObjectPool 类似ProxiedObjectPool
  • SynchronizedKeyedObjectPool 类似SynchronizedObjectPool
使⽤

scss

复制代码

new GenericObjectPool(PooledObjectFactory<T> factory)
new GenericObjectPool(PooledObjectFactory<T> factory, GenericObjectPoolConfig<T> config)
new GenericObjectPool(PooledObjectFactory<T> factory, GenericObjectPoolConfig<T> config, AbandonedConfig abando
nedConfig)

最重要的参数是PooledObjectFactory,⼀般来说,⼯⼚是需要我们⾃⼰根据业务需求去实现的。它是⽤来创建对象的,这其实就是设计模式⾥⾯的⼯⼚模式。

⽬前PooledObjectFactory有两个实现类。

  • BasePooledObjectFactory:抽象类,⽤于扩展⾃⼰的PooledObjectFactory
  • PoolUtils.SynchronizedPooledObjectFactory:内部类,代理⼀个其他的PooledObjectFactory,实现线程同步,⽤ PoolUtils.synchronizedPooledFactory() 创建

Factory核⼼⽅法:

  • makeObject 创建⼀个对象实例,并将其包装成⼀个PooledObject
  • destroyObject 销毁对象
  • validateObject 校验对象,确保对象池返回的对象是OK的
  • activateObject 重新初始化对象
  • passivateObject 取消初始化对象。GenericObjectPool的addIdleObject、returnObject、evict调⽤该⽅法。

Commons-Pool2总体分析

  • ObjectPool:对象池,最核⼼:GenericObjectPool、 GenericKeyedObjectPool。
  • Factory:创建&管理PooledObject,⼀般要⾃⼰扩展
  • PooledObject:包装原有的对象,从⽽让对象池管理,⼀般⽤DefaultPooledObject即可
Factory示例

java

复制代码

class MyPooledObjectFactory implements PooledObjectFactory<Model> { 
   public static final Logger LOGGER = LoggerFactory.getLogger(MyPooledObjectFactory.class); 
   @Override
   public PooledObject<Model> makeObject() throws Exception {
      DefaultPooledObject<Model> object = new DefaultPooledObject<>(new Model(1, "S")); 
      LOGGER.info("makeObject..state = {}", object.getState());
      return object; 
   }
  @Override
  public void destroyObject(PooledObject p) throws Exception{
      LOGGER.info("destroyObject..state = {}", object.getState());
   }
  @Override
  public boolean validateObject(PooledObject p) {
      LOGGER.info("validateObject..state = {}", object.getState());
      return true;
   }
  @Override
  public void activateObject(PooledObject p) throws Exception{
    LOGGER.info("activateObject..state = {}", p.getState());
   }
@Override
  public void passivateObject(PooledObject p) {
      LOGGER.info("passivateObject..state = {}", object.getState());
      return true;
   }

所有操作面向的都是PooledObject这个参数,makeObject返回的是PooledObject,其他API为什么操作的也是 PooledObject,⽽不是直接操作我们创建的对象呢?

这其实也是commons-pool设计巧妙之处。Pooledobject可以对原始对象进⾏包装,从⽽被对象池管理。⽬前 pooledobject有两个实现类:

  • DefaultPooledObject:包装原始对象,实现监控(例如创建时间、使⽤时间等)、状态跟踪等
  • PooledSoftReference:封装了DefaultPooledObject,⽤来和SoftReferenceObjectPool配合使⽤。
DefaultPooledObject定义了对象的若⼲种状态
  • IDLE 对象在队列中,并空闲。
  • ALLOCATED 使⽤中(即出借中)
  • EVICTION 对象当在队列中,正在进⾏驱逐测试
  • EVICTION_RETURN_TO_HEAD 对象驱逐测试通过后,放回到队列头部
  • VALIDATION 对象当前在队列中,空闲校验中
  • VALIDATION_PREALLOCATED 对象当前不在队列中,出借前校验中 VALIDATION_RETURN_TO_HEAD 对象当前不在队列中,校验通过后放回头部 INVALID 对象失效,驱逐测试失败、校验失败、对象销毁,都会将对象置为 INVALID。
  • ABANDONED 放逐中,如果对象上次使⽤时间超过removeAbandonedTimeout的配置,则将其标记为ABANDONED。标记为ABANDONED的对象即将变成 INVALID。
  • RETURNING 对象归还池中。

JVM调优

本系列专题将针对于Oracle Java HotSpot虚拟机为为开发者们提供不同的Java Heap内存空间的较为深入的分析介绍。对于任何接触的开发者都是非常重要的理论依据。频繁遇到的内存问题,提供生产环境的优化调整。那么适当的实战层级的Java虚拟机的内存空间分析能力是至关重要的。

前提概述

  • Java虚拟机是你的Java程序运行的基础,它为你提供动态的分配内存服务、垃圾收集、线程调度和切换、IO处理和本机操作等
  • Java堆空间是运行时Java程序的内存“容器”,它提供给您的Java应用程序所需的适当内存空间(Java堆、本机堆),并由JVM本身去管理。

JVM HotSpot内存被划分2类和5空间:

  • Heap堆内存空间:属于线程共享区域,也是我们JVM的内存管理范畴的最大的一部分运行时内存区域。
  • 方法区(永久代/元空间):属于线程共享区域,往往我们会忽略了这个区域的内存回收能力。
  • 本地堆 (C-Heap):本地方法的调用栈。
  • 虚拟机栈:Java方法的调用栈。


Heap堆内存空间

JVM的堆空间的变化在<18的版本之内,主要有一个分水岭,主要集中在8之前和8之后。

JDK8之前的对空间

JDK8之前的Heap空间如下图所示:


JDK8之后的Heap空间如下图所示:


主要时针对于方法区的实现机制:永久代 -> 元空间结构模型,接下来我们看看元数据空间在方法区中的分布结构模型。

后续版本中的-元空间和方法去的内存才能出分配关系


可以看到JDK8之后,方法去的实现有元空间和一部分堆内存组成。之前主要只有单纯的永久代去实现的。

常量池

常量池主要有静态常量池和运行时常量池组成。

  • 类信息
  • 类的版本
  • 字段描述信息
  • 方法描述信息
  • 接口和父类等描述信息
  • class文件常量池(静态常量池)
静态常量池,也叫class⽂件常量池,主要存放:
  • 字⾯量:例如⽂本字符串、 final修饰的常量。
  • 符号引⽤:例如类和接⼝的全限定名、字段的名称和描述符、⽅法的名称和描述符。
运⾏时常量池

当类加载到内存中后,JVM就会将静态常量池中的内容存放到运⾏时的常量池中;运⾏时常量池⾥⾯存储的主要是编译期间⽣成的字⾯量、符号引⽤等等。如下图对应的字符串常量在字符串常量池中的存储模式。



字符串常量池

字符串常量池,也可以理解成运⾏时常量池分出来的⼀部分,类加载到内存的时候,字符串,会存到字符串常量池⾥⾯。



对象和类在内存分布

针对于代码的执行和存储在JVM的分布,主要集中在栈空间和堆空间、方法区。它们各个的职能不同,对应的能力也是不同的。我们针对于一段代码块进行分析和介绍

虚拟机栈的基本结构模型



代码在堆栈中的存储结构信息



内存泄漏怎么排查[java内存溢出排查]

top 等查看系统内存概况

top:显示所有进程运行情况,按M键按照内存大小排序。

使用格式

css

复制代码

top [-] [d] [p] [q] [c] [C] [S] [s] [n]

参数说明

  • d:指定每两次屏幕信息刷新之间的时间间隔,当然用户可以使用s交互命令来改变之
  • p:通过指定监控进程ID来仅仅监控某个进程的状态。
  • q:该选项将使top没有任何延迟的进行刷新。如果调用程序有超级用户权限,那么top将以尽可能高的优先级运行。
  • S:指定累计模式。
  • s:使top命令在安全模式中运行。这将去除交互命令所带来的潜在危险。
  • i:使top不显示任何闲置或者僵死进程。
  • c:显示整个命令行而不只是显示命令名。

命令说明

  • jmx 快速发现jvm中的内存异常项

【实战阶段】JVM排查问题优化参数

css

复制代码

jps [-q] [-mlvV] [<hostid>]

参数如下:

  • -q 只显示进程号
  • -m 显示传递给main⽅法的参数
  • -l 显示应⽤main class的完整包名应⽤的jar⽂件完整路径名
  • -v 显示传递给JVM的参数
  • -V 禁⽌输出类名、JAR⽂件名和传递给main⽅法的参数,仅显示本地JVM标识符的列表

hostid的参数格式

  • hostid:想要查看的主机的标识符,格式为: [protocol:][[//]hostname][:port][/servername] ,其中:
  • protocol:通信协议,默认rmi
  • hostname:⽬标主机的主机名或IP地址
  • port:通信端⼝,对于默认 rmi 协议,该参数⽤来指定 rmiregistry 远程主机上的端⼝号。如省略该参数,并且该
  • protocol指示rmi,则使⽤默认使⽤1099端⼝。
  • servicename:服务名称,取值取决于实现⽅式,对于rmi协议,此参数代表远程主机上RMI远程对象的名称

今天就写到这里,未完待续,等待下一部分的内容。

相关文章
|
16天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
36 6
|
12天前
|
监控 Java 编译器
Java虚拟机调优实战指南####
本文深入探讨了Java虚拟机(JVM)的调优策略,旨在帮助开发者和系统管理员通过具体、实用的技巧提升Java应用的性能与稳定性。不同于传统摘要的概括性描述,本文摘要将直接列出五大核心调优要点,为读者提供快速预览: 1. **初始堆内存设置**:合理配置-Xms和-Xmx参数,避免频繁的内存分配与回收。 2. **垃圾收集器选择**:根据应用特性选择合适的GC策略,如G1 GC、ZGC等。 3. **线程优化**:调整线程栈大小及并发线程数,平衡资源利用率与响应速度。 4. **JIT编译器优化**:利用-XX:CompileThreshold等参数优化即时编译性能。 5. **监控与诊断工
|
12天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
17天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
18天前
|
JSON 前端开发 JavaScript
java-ajax技术详解!!!
本文介绍了Ajax技术及其工作原理,包括其核心XMLHttpRequest对象的属性和方法。Ajax通过异步通信技术,实现在不重新加载整个页面的情况下更新部分网页内容。文章还详细描述了使用原生JavaScript实现Ajax的基本步骤,以及利用jQuery简化Ajax操作的方法。最后,介绍了JSON作为轻量级数据交换格式在Ajax应用中的使用,包括Java中JSON与对象的相互转换。
33 1
|
24天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
64 5
|
21天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
32 1
|
23天前
|
监控 前端开发 Java
Java SpringBoot –性能分析与调优
Java SpringBoot –性能分析与调优
|
24天前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
39 3
|
24天前
|
SQL 监控 Java
Java连接池技术的最新发展,包括高性能与低延迟、智能化管理与监控、扩展性与兼容性等方面
本文探讨了Java连接池技术的最新发展,包括高性能与低延迟、智能化管理与监控、扩展性与兼容性等方面。同时,结合最佳实践,介绍了如何选择合适的连接池库、合理配置参数、使用监控工具及优化数据库操作,以实现高效稳定的数据库访问。示例代码展示了如何使用HikariCP连接池。
13 2
下一篇
无影云桌面