高级Java性能优化的七个方向

下栽地止:https://lexuecode.com/6572.html

1、复用优化

在写代码的时候,你会发现有很多重复的代码可以提取出来,做成公共的方法。这样,在下次用的时候,就不用再费劲写一遍了。

这种思想就是复用。上面的描述是编码逻辑上的优化,对于数据存取来说,有同样的复用情况。无论是在生活中还是编码中,重复的事情一直在发生,如果没有复用,工作和生活就会比较累。

在软件系统中,谈到数据复用,我们首先想到的就是缓冲和缓存。注意这两个词的区别,它们的意义是完全不同的,很多同学很容易搞混,在这里简单地介绍一下。

缓冲(Buffer),常见于对数据的暂存,然后批量传输或者写入。多使用顺序方式,用来缓解不同设备之间频繁地、缓慢地随机写,缓冲主要针对的是写操作。

缓存(Cache),常见于对已读取数据的复用,通过将它们缓存在相对高速的区域,缓存主要针对的是读操作。

与之类似的,是对于对象的池化操作,比如数据库连接池、线程池等,在 Java 中使用得非常频繁。由于这些对象的创建和销毁成本都比较大,我们在使用之后,也会将这部分对象暂时存储,下次用的时候,就不用再走一遍耗时的初始化操作了。

2、 计算优化
并行执行
现在的 CPU 发展速度很快,绝大多数硬件,都是多核。要想加快某个任务的执行,最快最优的解决方式,就是让它并行执行。并行执行有以下三种模式。

第一种模式是多机,采用负载均衡的方式,将流量或者大的计算拆分成多个部分,同时进行处理。 比如,Hadoop 通过 MapReduce 的方式,把任务打散,多机同时进行计算。

第二种模式是采用多进程。 比如 Nginx,采用 NIO 编程模型,Master 统一管理 Worker 进程,然后由 Worker 进程进行真正的请求代理,这也能很好地利用硬件的多个 CPU。

第三种模式是使用多线程,这也是 Java 程序员接触最多的。 比如 Netty,采用 Reactor 编程模型,同样使用 NIO,但它是基于线程的。 Boss 线程用来接收请求,然后调度给相应的 Worker 线程进行真正的业务计算。

像 Golang 这样的语言,有更加轻量级的协程(Coroutine),协程是一种比线程更加轻量级的存在,但目前在 Java 中还不太成熟,就不做过多介绍了,但本质上,它也是对于多核的应用,使得任务并行执行。

变同步为异步
再一种对于计算的优化,就是变同步为异步,这通常涉及编程模型的改变。同步方式,请求会一直阻塞,直到有成功,或者失败结果的返回。虽然它的编程模型简单,但应对突发的、时间段倾斜的流量,问题就特别大,请求很容易失败。

异步操作可以方便地支持横向扩容,也可以缓解瞬时压力,使请求变得平滑。同步请求,就像拳头打在钢板上;异步请求,就像拳头打在海绵上。你可以想象一下这个过程,后者肯定是富有弹性的,体验更加友好。

惰性加载
最后一种,就是使用一些常见的设计模式来优化业务,提高体验,比如单例模式、代理模式等。举个例子,在绘制 Swing 窗口的时候,如果要显示比较多的图片,就可以先加载一个占位符,然后通过后台线程慢慢加载所需要的资源,这就可以避免窗口的僵死。

3、结果集优化
接下来介绍一下对结果集的优化。举个比较直观的例子,我们都知道 XML 的表现形式是非常好的,那为什么还有 JSON 呢?除了书写要简单一些,一个重要的原因就是它的体积变小了,传输效率和解析效率变高了,像 Google 的 Protobuf,体积就更小了一些。虽然可读性降低,但在一些高并发场景下(如 RPC),能够显著提高效率,这是典型的对结果集的优化。

这是由于我们目前的 Web 服务,都是 C/S 模式。 数据从服务器传输到客户端,需要分发多份,这个数据量是急剧膨胀的,每减少一小部分存储,都会有比较大的传输性能和成本提升。

像 Nginx,一般都会开启 GZIP 压缩,使得传输的内容保持紧凑。 客户端只需要一小部分计算能力,就可以方便解压。 由于这个操作是分散的,所以性能损失是固定的。

了解了这个道理,我们就能看到对于结果集优化的一般思路,你要尽量保持返回数据的精简。 一些客户端不需要的字段,那就在代码中,或者直接在 SQL 查询中,就把它去掉。

对于一些对时效性要求不高,但对处理能力有高要求的业务。 我们要吸取缓冲区的经验,尽量减少网络连接的交互,采用批量处理的方式,增加处理速度。

结果集合很可能会有二次使用,你可能会把它加入缓存中,但依然在速度上有所欠缺。 这个时候,就需要对数据集合进行处理优化,采用索引或者 Bitmap 位图等方式,加快数据访问速度。

4、资源冲突优化
我们在平常的开发中,会涉及很多共享资源。这些共享资源,有的是单机的,比如一个 HashMap;有的是外部存储,比如一个数据库行;有的是单个资源,比如 Redis 某个 key 的Setnx;有的是多个资源的协调,比如事务、分布式事务等。

现实中的性能问题,和锁相关的问题是非常多的。大多数我们会想到数据库的行锁、表锁、Java 中的各种锁等。在更底层,比如 CPU 命令级别的锁、JVM 指令级别的锁、操作系统内部锁等,可以说无处不在。

只有并发,才能产生资源冲突。也就是在同一时刻,只能有一个处理请求能够获取到共享资源。解决资源冲突的方式,就是加锁。再比如事务,在本质上也是一种锁。

按照锁级别,锁可分为乐观锁和悲观锁,乐观锁在效率上肯定是更高一些;按照锁类型,锁又分为公平锁和非公平锁,在对任务的调度上,有一些细微的差别。

对资源的争用,会造成严重的性能问题,所以会有一些针对无锁队列之类的研究,对性能的提升也是巨大的。

5、算法优化
算法能够显著提高复杂业务的性能,但在实际的业务中,往往都是变种。由于存储越来越便宜,在一些 CPU 非常紧张的业务中,往往采用空间换取时间的方式,来加快处理速度。

算法属于代码调优,代码调优涉及很多编码技巧,需要使用者对所使用语言的 API 也非常熟悉。有时候,对算法、数据结构的灵活使用,也是代码优化的一个重要内容。比如,常用的降低时间复杂度的方式,就有递归、二分、排序、动态规划等。

一个优秀的实现,比一个拙劣的实现,对系统的影响是非常大的。比如,作为 List 的实现,LinkedList 和 ArrayList 在随机访问的性能上,差了好几个数量级;又比如,CopyOnWriteList 采用写时复制的方式,可以显著降低读多写少场景下的锁冲突。而什么时候使用同步,什么时候是线程安全的,也对我们的编码能力有较高的要求。

这部分的知识,就需要我们在平常的工作中注意积累,后面的课时中,也会挑比较重要的知识点穿插讲解。

6、高效实现
在平时的编程中,尽量使用一些设计理念良好、性能优越的组件。比如,有了 Netty,就不用再选择比较老的 Mina 组件。而在设计系统时,从性能因素考虑,就不要选 SOAP 这样比较耗时的协议。再比如,一个好的语法分析器(比如使用 JavaCC),其效率会比正则表达式高很多。

总之,如果通过测试分析,找到了系统的瓶颈点,就要把关键的组件,使用更加高效的组件进行替换。在这种情况下,适配器模式是非常重要的。这也是为什么很多公司喜欢在现有的组件之上,再抽象一层自己的;而当在底层组件进行切换的时候,上层的应用并无感知。

7、JVM 优化
因为 Java 是运行在 JVM 虚拟机之上,它的诸多特性,就要受到 JVM 的制约。对 JVM 虚拟机进行优化,也能在一定程度上能够提升 JAVA 程序的性能。如果参数配置不当,甚至会造成 OOM 等比较严重的后果。

目前被广泛使用的垃圾回收器是 G1,通过很少的参数配置,内存即可高效回收。CMS 垃圾回收器已经在 Java 14 中被移除,由于它的 GC 时间不可控,有条件应该尽量避免使用。

JVM 性能调优涉及方方面面的取舍,往往是牵一发而动全身,需要全盘考虑各方面的影响。所以了解 JVM 内部的一些运行原理,还是特别重要的,它有益于我们加深对代码更深层次的理解,帮助我们书写出更高效的代码。

以上就是代码优化的 7 个大方向,我们通过简要的介绍,让大家对性能优化的内容有了大体的了解。这7大方向是代码优化的最主要方向,当然,性能优化还包含数据库优化、操作系统优化、架构优化等其他一些内容,这些不是我们的重点,在后面的文章中,我们也只做简要的介绍。

极客时间高级Java - JVM 源码分析之 JVM 启动流程

JVM 源码分析之 JVM 启动流程

执行 Java 类的 main 方法,程序就能运行起来,main 方法的背后,虚拟机究竟发生了什么?如果你对这个感兴趣,相信本文会给你一个答案,本文分析的 openjdk 版本为 openjdk-7-fcs-src-b147-27

class BootStrap {
public static void main(String[] args) {
for (String str : args) {
System.out.println(str);
}
}}
java BootStrap -Xms6G -Xmx8G -Xmn3G -Xss512k
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
虚拟机的启动入口位于share/tools/launcher/java.c的 main 方法,整个流程分为如下几个步骤:

1、配置 JVM 装载环境

2、解析虚拟机参数

3、设置线程栈大小

4、执行 Java main 方法

1、配置 JVM 装载环境
Java 代码执行时需要一个 JVM 环境,JVM 环境的创建包括两部分:JVM.dll 文件的查找和装载。

JVM.dll 文件的查找
通过CreateExecutionEnvironment方法实现,根据当前 JRE 环境的路径和系统版本寻找jvm.cfg文件,windows 实现如下:

大概实现逻辑:

1、GetJREPath查找当前 JRE 环境的所在路径;

2、ReadKnownVms读取JRE路径\lib\ARCH(CPU构架)\JVM.cfg文件,其中ARCH(CPU构架)通过GetArch方法获取,在 window 下有三种情况:amd64、ia64 和 i386;

3、CheckJvmType确定当前 JVM 类型,先判断是否通过-J、-XXaltjvm=或-J-XXaltjvm=参数指定,如果没有,则读取 JVM.cfg 文件中配置的第一个类型;

4、GetJVMPath根据上一步确定的 JVM 类型,找到对应的 JVM.dll 文件;

JVM.dll 文件的装载
初始化虚拟机中的函数调用,即通过 JVM 中的方法调用 JVM.dll 文件中定义的函数,实现如下:

1、LoadLibrary方法装载 JVM.dll 动态连接库;

2、把 JVM.dll 文件中定义的函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs绑定到 InvocationFunctions 变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上;

2、虚拟机参数解析
装载完 JVM 环境之后,需要对启动参数进行解析,其实在装载 JVM 环境的过程中已经解析了部分参数,该过程通过ParseArguments方法实现,并调用AddOption方法将解析完成的参数保存到JavaVMOption中,JavaVMOption结构实现如下:

AddOption 方法实现如下:

这里对-Xss参数进行特殊处理,并设置 threadStackSize,因为参数格式比较特殊,其它是 key/value 键值对,它是-Xss512的格式。后续Arguments类会对JavaVMOption数据进行再次处理,并验证参数的合理性。

参数处理
Arguments::parse_each_vm_init_arg

方法负责处理经过解析过的 JavaVMOption 数据,部分实现如下:

1、-Xmn:设置新生代的大小 NewSize 和 MaxNewSize;

2、-Xms:设置堆的初始值 InitialHeapSize,也是堆的最小值;

3、-Xmx:设置堆的最大值 MaxHeapSize;

参数验证
Arguments::check_gc_consistency

方法负责验证虚拟机启动参数中配置 GC 的合理性,实现如下:

1、如果参数为-XX:+UseSerialGC -XX:+UseParallelGC,由于 UseSerialGC 和 UseParallelGC 不能兼容,JVM 启动时会抛出错误信息;

2、如果参数为-XX:+UseConcMarkSweepGC -XX:+UseParNewGC,其中 UseConcMarkSweepGC 和 UseParNewGC 可以兼容,JVM 可以正常启动;

3、设置线程栈大小

如果启动参数未设置-Xss,即 threadStackSize 为 0,则调用 InvocationFunctions 的GetDefaultJavaVMInitArgs方法获取 JavaVM 的初始化参数,即调用 JVM.dll 函数JNI_GetDefaultJavaVMInitArgs,定义在share\vm\prims\jni.cpp,实现如下:

ThreadStackSize定义在globals.hpp中,根据当前系统类型,加载对应的配置文件,所以在不同的系统中,ThreadStackSize的默认值也不同。

4、执行 Java main 方法

线程栈大小确定后,通过ContinueInNewThread方法创建新线程,并执行 JavaMain 函数,JavaMain 函数的大概流程如下:

1、新建 JVM 实例
InitializeJVM方法调用 InvocationFunctions 的CreateJavaVM方法,即调用 JVM.dll 函数

JNI_CreateJavaVM,新建一个 JVM 实例,该过程比较复杂,会在后续文章进行分析;

2、加载主类的 class
Java 运行方式有两种:jar 方式和 class 方式。

jar 方式

1、调用GetMainClassName方法找到META-INF/MANIFEST.MF文件指定的 Main-Class 的主类名;

2、调用LoadClass方法加载主类的 class 文件;

class 方式

1、调用NewPlatformString方法创建类名的 String 对象;

2、调用LoadClass方法加载主类的 class 文件;

3、查找 main 方法
通过GetStaticMethodID方法查找指定方法名的静态方法,实现如下:

最终调用JVM.dll函数jni_GetStaticMethodID实现

其中get_method_id方法根据类文件对应的 instanceKlass 对象查找指定方法。

4、执行 main 方法

1、重新创建参数数组;

2、其中 mainID 是 main 方法的入口地址,CallStaticVoidMethod方法最终调用JVM.dll中的jni_CallStaticVoidMethodV函数,实现如下

jni_invoke_static 实现如下:

最终通过JavaCalls::call执行 main 方法。

极客时间高级Java工程师体系课2.0相关推荐

  1. 极客时间-如何设计一个秒杀系统-笔记0到2章

    极客时间-如何设计一个秒杀系统-笔记0到2章 0.开篇词-系统秒杀系统架构设计都有哪些关键点? 1.设计秒杀系统时应该注意的5个架构原则 1.数据要尽量少 2.请求数要尽量少 3.路径要尽量少 4.依 ...

  2. 极客时间《Java并发编程》学习笔记

      该领域是跟着"极客时间"平台的课程<Java并发编程>入门的,讲师是王宝令.说实话,这个领域如果抱着大部头的书就啃的话,由于缺少实践很难会建立一个体系,即不知道为什 ...

  3. 打包带走极客时间大数据课程的正确姿势

    "如何持续学习大数据 ,实现高效进阶?" 经常有读者让我推荐学习资源,非常开心大家能一直保持学习的习惯.其实不仅仅是大数据工程师需要学习大数据,每个软件工程师都应该学习一些大数据知 ...

  4. 【极客时间】《Java并发编程实战》学习笔记

    目录: 开篇词 | 你为什么需要学习并发编程? 内容来源:开篇词 | 你为什么需要学习并发编程?-极客时间 例如,Java 里 synchronized.wait()/notify() 相关的知识很琐 ...

  5. 笔记1 第11课 贪心初步 ——柠檬水找零,分发饼干,跳跃游戏,完成所有任务所需最小能量——极客时间算法

    之前收藏了极客时间的算法训练营3期 共21课,计划每一课写博客来记录学习,主要形式为 方法类型1 题1 题解 题2 题解 方法类型2 题1 题解 -- 题目大体来自leetcode 和 acwing ...

  6. 我和极客时间合作了一门产品创新课

    算起来,正好是三个月前,在台北,参加了一个极客邦组织的全球技术领导力峰会GTLC. 详情如文:刚去台湾参了个会,就听说自由行受限了 会上碰到了主办方的老板霍泰稳,聊了一会儿以后,他就邀请我做一门和产品 ...

  7. java爬虫抓取极客时间专栏页面

    0.clone代码导入idea 项目地址: github项目 1.登录极客时间账号,查看cookie信息.在代码com.ady01.demo4.jksj.util.CollectorUtil#COOK ...

  8. 笔记1 第13课 动规2 ——买卖股票最佳时机,打家劫舍,零钱找兑——极客时间算法

    之前收藏了极客时间的算法训练营3期 共21课,计划每一课写博客来记录学习,主要形式为 方法类型1 题1 题解 题2 题解 方法类型2 题1 题解 -- 题目大体来自leetcode 和 acwing ...

  9. 极客时间「大师课·深度剖析 RocketMQ5.0」上线啦,欢迎免费领取!

    从初代开源消息队列崛起,到 PC 互联网.移动互联网爆发式发展,再如今 IoT.云计算.云原生引领了新的技术趋势,消息中间件的发展已经走过了 30 多个年头. 目前,消息中间件在国内许多行业的关键应用 ...

最新文章

  1. 刻意练习摆脱简单重复
  2. (转载)MyEclipse github
  3. Ajax — 大事件项目(第二天)
  4. oracle容错,Oracle DML容错处理方法
  5. 14 操作系统第四章 文件管理 文件逻辑结构 文件目录结构
  6. flask与js交互的示例代码_QT5.12 C++与前端JavaScript/HTML实现通信交互
  7. C++查看数据存储大小端模式
  8. 蓝桥杯vip答案java_Java实现 蓝桥杯VIP 算法训练 寂寞的数
  9. react-native学习小结
  10. 五种常见的linux系统有哪些,常见的Linux操作系统有哪些
  11. 大小端转换代码(宏、函数方式)(浮点、整数)
  12. 翻转课堂管理系统_ER图_功能图_数据字典_数据库脚本
  13. sdelete使用指南(Windows Sysinternals Suite工具介绍转)
  14. html数字自动滚动代码,HTML+JS实现滚动数字的时钟
  15. error: metrics not available yet
  16. 游戏语音SDK解决回声消除的方案
  17. 蒋凡出海,阿里的必答题是全球化
  18. java 图片合成 工具类_Java实现的微信图片处理工具类【裁剪,合并,等比例缩放等】...
  19. 消息系统(ActiveMQ)
  20. Flash&Flex大全

热门文章

  1. mac系统谷歌浏览器一些问题
  2. VLAN应用篇系列:(10)H3C交换机 PVLAN功能(V7为PVLAN,V5为isolate-user-vlan)
  3. 家庭级心率监测设备 哪种才最方便有效
  4. ACM练级日志:带权并查集与食物链
  5. pythonorm_python中ORM是什么?
  6. 2021-08-29 网安实验-WEB专题-捉迷藏
  7. JAVA利用海明距离测算文本相似度
  8. CSUST - 2021 组队选拔赛
  9. 实至名归 | 企企通入选《2022爱分析· 工业互联网厂商全景报告》
  10. jQuery ajaxSetup() 全局默认配置