共计 4497 个字符,预计需要花费 12 分钟才能阅读完成。
前言
JVM 调优是每个高级程序员的必修课,在本章中,我会从倒退过程以及外围价值来分析 JVM 的体系结构。为了让大家更好的了解 JVM 的工作机制,
我会在解说完运行时数据区之后,再通过一个类的加载过程到这个类最终在运行时数据区中的存储来更进一步了解 JVM 的工作原理。最初,通过对内存的回收机制和垃圾回收算法的解说,引出到 JVM 的性能调优这一主题,在这个局部会着重解说垃圾回收算法以及常见的垃圾回收器的区别和应用场景。
JVM 内存区域划分
程序计数器(线程公有)
程序计数器(Program Counter Register),也有称作为 PC 寄存器。保留的是程序当 前执行的指令的地址(也能够说保留下一条指令的所在存储单元的地址),当 CPU 须要执 行指令时,须要从程序计数器中失去以后须要执行的指令所在存储单元的地址,而后依据得 到的地址获取到指令,在失去指令之后,程序计数器便主动加 1 或者依据转移指针失去下 一条指令的地址,如此循环,直至执行完所有的指令。也就是说是用来批示执行哪条指令的。
java 栈
Java 栈中寄存的是一个个的栈帧,每个栈帧对应一个被调用的办法,在栈帧中包含局 部变量表、操作数栈、指向以后办法所属的类的运行时常量池的援用、办法返回地址、额 外的附加信息。当线程执行一个办法时,就会随之创立一个对应的栈帧,并将建设的栈帧压 栈。当办法执行结束之后,便会将栈帧出栈。因而可知,线程以后执行的办法所对应的栈帧 必然位于 Java 栈的顶部。
本地办法栈
本地办法栈与 Java 栈的作用和原理十分类似。区别只不过是 Java 栈是为执行 Java 办法服务的,而本地办法栈则是为执行本地办法(Native Method)服务的。在 JVM 规 范中,并没有对本中央倒退的具体实现办法以及数据结构作强制规定,虚拟机能够自在实现 它。在 HotSopt 虚拟机中间接就把本地办法栈和 Java 栈合二为一。
堆
Java 中的堆是用来存储对象自身的以及数组(当然,数组援用是寄存在 Java 栈中的),堆是被所有线程共享的,在 JVM 中只有一个堆。所有对象实例以及数组都要在堆上调配内 存,单随着 JIT 倒退,栈上调配,标量替换优化技术,在堆上调配变得不那么到相对,只能 在 server 模式下能力启用逃逸剖析。
办法区
办法区中,存储了每个类的信息(包含类的名称、办法信息、字段信息)、动态变 量、常量以及编译器编译后的代码等。在 Class 文件中除了类的字段、办法、接口等形容信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号援用。
间接内存
NIO, 应用 native 函数库间接调配堆外内存,不通过 JVM 内存间接拜访零碎物理内存的类 ——DirectBuffer。DirectBuffer 类继承自 ByteBuffer,但和一般的 ByteBuffer 不同,一般的 ByteBuffer 仍在 JVM 堆上分配内存,其最大内存受到最大堆内存的限度;而 DirectBuffer 间接调配在物理内存中,并不占用堆空间,其可申请的最大内存受操作系统 限度
JVM 执行子系统
Class 类文件构造
各种不同平台的虚拟机与所有平台都对立应用的程序存储格局——字节码(ByteCode)是 形成平台无关性的基石,也是语言无关性的根底。Java 虚拟机不和包含 Java 在内的任何语 言绑定,它只与“Class 文件”这种特定的二进制文件格式所关联,Class 文件中蕴含了 Java 虚拟机指令集和符号表以及若干其余辅助信息。
Java 跨平台的根底
各种不同平台的虚拟机与所有平台都对立应用的程序存储格局——字节码(ByteCode)是 形成平台无关性的基石,也是语言无关性的根底。Java 虚拟机不和包含 Java 在内的任何语 言绑定,它只与“Class 文件”这种特定的二进制文件格式所关联,Class 文件中蕴含了 Java 虚拟机指令集和符号表以及若干其余辅助信息。
Class 类的实质
任何一个 Class 文件都对应着惟一一个类或接口的定义信息,但反过来说,Class 文件理论 上它并不一定以磁盘文件的模式存在。Class 文件是一组以 8 位字节为根底单位的二进制流。
字节码指令
Java 虚拟机的指令由一个字节长度的、代表着某种特定操作含意的数字(称为操作码,Opcode)以及追随其后的零至多个代表此操作所需参数(称为操作数,Operands)而形成。因为限度了 Java 虚拟机操作码的长度为一个字节(即 0~255),这意味着指令集的操作 码总数不可能超过 256 条。
大多数的指令都蕴含了其操作所对应的数据类型信息。例如:iload 指令用于从局部变量表中加载 int 型的数据到操作数栈中,而 fload 指令加载的则是 float 类型的数据。
大部分的指令都没有反对整数类型 byte、char 和 short,甚至没有任何指令反对 boolean 类 型。大多数对于 boolean、byte、short 和 char 类型数据的操作,实际上都是应用相应的 int 类型作为运算类型
类加载机制
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包含:加载(Loading)、验证(Verification)、筹备(Preparation)、解析(Resolution)、初始化(Initialization)、应用(Using)和卸载(Unloading)7 个阶段。其中验证、筹备、解析 3 个局部统称为连贯(Linking)于初始化阶段,虚拟机标准则是严格规定了有且只有 5 种状况必须立刻对类进行“初始化”(而加载、验证、筹备天然须要在此之前开始)
垃圾回收器和内存调配策略
Java 中是值传递还是援用传递?
在运行栈中,根本类型和援用的解决是一样的,都是传 值,所以,如果是传援用的办法调用,也同时能够了解为“传援用值”的传值调用,即援用的 解决跟根本类型是齐全一样的。然而当进入被调用办法时,被传递的这个援用的值,被程序 解释(或者查找)到堆中的对象,这个时候才对应到真正的对象。如果此时进行批改,修 改的是援用对应的对象,而不是援用自身,即:批改的是堆中的数据。所以这个批改是能够 放弃的了!
对象,从某种意义上说,是由根本类型组成的。能够把一个对象看作为一棵树,对象的属性 如果还是对象,则还是一颗树(即非叶子节点),根本类型则为树的叶子节点。程序参数传 递时,被传递的值自身都是不能进行批改的,然而,如果这个值是一个非叶子节点(即一个 对象援用),则能够批改这个节点上面的所有内容。
援用类型
对象援用类型分为强援用、软援用、弱援用和虚援用。强援用: 就是咱们个别申明对象是时虚拟机生成的援用,强援用环境下,垃圾回收时须要严 格判断以后对象是否被强援用,如果被强援用,则不会被垃圾回收
软援用
软援用个别被做为缓存来应用。与强援用的区别是,软援用在垃圾回收时,虚拟机 会依据以后零碎的残余内存来决定是否对软援用进行回收。如果残余内存比拟缓和,则虚构 机会回收软援用所援用的空间;如果残余内存绝对富裕,则不会进行回收。换句话说,虚构 机在产生 OutOfMemory 时,必定是没有软援用存在的。
弱援用
弱援用与软援用相似,都是作为缓存来应用。但与软援用不同,弱援用在进行垃圾 回收时,是肯定会被回收掉的,因而其生命周期只存在于一个垃圾回收周期内。强援用不用说,咱们零碎个别在应用时都是用的强援用。而“软援用”和“弱援用”比拟少见。他们个别被作为缓存应用,而且个别是在内存大小比拟受限的状况下做为缓存。因为如果内 存足够大的话,能够间接应用强援用作为缓存即可,同时可控性更高。因此,他们常见的是 被应用在桌面利用零碎的缓存。
根本垃圾回收算法
援用计数(Reference Counting):
比拟古老的回收算法。原理是此对象有一个援用,即减少一个计数,删除一个援用则缩小一 个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无奈解决循环援用的 问题。
可达性剖析清理
标记 - 革除(Mark-Sweep):
此算法执行分两阶段。第一阶段从援用根节点开始标记所有被 援用的对象,第二阶段遍历整个堆,把未标记的对象革除。此算法须要暂停整个利用,同时,会产生内存碎片
复制(Copying):
此算法把内存空间划为两个相等的区域,每次只应用其中一个区域。垃 圾回收时,遍历以后应用区域,把正在应用中的对象复制到另外一个区域中。次算法每次只 解决正在应用中的对象,因而复制老本比拟小,同时复制过来当前还能进行相应的内存整理,不会呈现“碎片”问题。当然,此算法的毛病也是很显著的,就是须要两倍内存空间。
标记 - 整顿(Mark-Compact):
此算法联合了“标记 - 革除”和“复制”两个算法的长处。也是分 两阶段,第一阶段从根节点开始标记所有被援用对象,第二阶段遍历整个堆,革除标记对 象,并未标记对象并且把存活对象“压缩”到堆的其中一块,按程序排放。此算法防止了“标 记 - 革除”的碎片问题,同时也防止了“复制”算法的空间问题。
性能优化
一个 web 利用不是一个孤立的个体,它是一个零碎的局部,零碎中的每一部分都会影响整 个零碎的性能
罕用的性能评估 / 测试指标
响应工夫
提交申请和返回该申请的响应之间应用的工夫,个别比拟关注均匀响应工夫。罕用操作的响应工夫列表:
并发数
同一时刻,对服务器有理论交互的申请数。和网站在线用户数的关联:1000 个同时在线用户数,能够预计并发数在 5% 到 15% 之间,也就是同时并发数在 50~150 之间。
吞吐量
对单位工夫内实现的工作量 (申请) 的量度
关系
零碎吞吐量和零碎并发数以及响应工夫的关系:了解为高速公路的通行情况:吞吐量是每天通过收费站的车辆数目(能够换算成收费站收取的高速费),并发数是高速公路上的正在行驶的车辆数目,响应工夫是车速。车辆很少时,车速很快。然而收到的高速费也相应较少;随着高速公路上车辆数目的增多,车速略受影响,然而收到的高速费减少很快;随着车辆的持续减少,车速变得越来越慢,高速公路越来越堵,免费不增反降;如果车流量持续减少,超过某个极限后,工作偶尔因素都会导致高速全副瘫痪,车走不动,当然后也收不着,而高速公路成了停车场(资源耗尽)。
罕用的性能优化伎俩
防止过早优化
不应该把大量的工夫消耗在小的性能改良上,过早思考优化是所有噩梦的本源。所以,咱们应该编写清晰,间接,易读和易了解的代码,真正的优化应该留到当前,等到性 能分析表明优化措施有微小的收益时再进行。
进行零碎性能测试
所有的性能调优,都有应该建设在性能测试的根底上,直觉很重要,然而要用数据谈话,可 以揣测,然而要通过测试求证。
寻找零碎瓶颈,分而治之,逐渐优化
性能测试后,对整个申请经验的各个环节进行剖析,排查呈现性能瓶颈的中央,定位问题,剖析影响性能的的次要因素是什么?内存、磁盘 IO、网络、CPU,还是代码问题?架构设 计有余?或者的确是系统资源有余?
小结
因为文章篇幅起因,更多的细节知识点曾经写不完了,我全副总结在上面这份【JVM 与性能调优知识点】外面了,各位需要的话能够关注我的公众号 前程有光 收费支付
最初
欢送关注公众号:前程有光,支付这份【JVM 与性能调优知识点】+ 一线大厂 Java 面试题总结 + 各知识点学习思维导 + 一份 300 页 pdf 文档的 Java 外围知识点总结!