剑指JVM:虚拟机实践与性能调优-尚硅谷教育
- 书名: 剑指JVM:虚拟机实践与性能调优
- 作者: 尚硅谷教育
- 简介: 《剑指JVM:虚拟机实践与性能调优》共分5篇:引言篇讲述了Java与Java虚拟机的关系,以及Java虚拟机的相关知识;第1篇讲述了运行时数据区,涉及Java内存区域的各个核心结构,以及对象创建的各种细节;第2篇讲述了垃圾收集,涉及各种收集算法、垃圾收集器;第3篇讲述了字节码与类的加载;第4篇讲述了性能监控与调优,带领读者学习Java虚拟机常用的监控与调优工具,并附有企业级的性能调优案例。本书配套视频,可以关注尚硅谷教育公众号获取。
- 出版时间 2023-05-01 00:00:00
- ISBN: 9787302628118
- 分类: 计算机-计算机综合
- 出版社: 清华大学出版社
高亮划线
封面
版权信息
作者简介
内容简介
前言
引言篇
-
📌 Java编译器输入的指令流是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构。 ^6-12918-12967
- ⏱ 2023-09-27 19:46:36
-
📌 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主。 ^6-13638-13703
- ⏱ 2023-12-13 12:48:14
-
📌 我们来总结一下,由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。基于栈式架构的优点是跨平台、指令集小、编译器容易实现,缺点是性能较差,实现同样的功能需要更多的指令。 ^6-18246-18357
- ⏱ 2023-12-13 12:53:23
-
📌 执行一个Java程序的时候,真正在执行的是一个叫作JVM的进程,通常情况下,一个Java程序对应一个JVM进程。 ^6-18898-18954
- ⏱ 2023-12-13 12:54:37
-
📌 2018年4月,Oracle Labs新公开了一项黑科技:Graal VM,如图1-17所示。从它的口号“Run Programs Faster Anywhere”就能感觉到一颗蓬勃的野心,这句话显然是与1995年Java刚诞生时的“Write Once,Run Anywhere”遥相呼应。 ^6-26540-26686
- ⏱ 2023-09-27 20:26:46
第1篇 运行时数据区篇
- 📌 在HotSpot虚拟机中,每个线程都与操作系统的本地线程直接映射。当一个Java线程准备好执行以后,此时这个操作系统的本地线程也会同时创建。Java线程执行终止后,本地线程也会回收。 ^7-3058-3149
- ⏱ 2023-09-27 20:31:24
第3章 程序计数器
-
📌 程序计数器是一块较小的内存空间, ^8-842-858
- ⏱ 2023-09-27 20:34:15
-
📌 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。此内存区域是唯一一个在“Java虚拟机规范”中没有规定任何OutOfMemoryError情况的区域。程序计数器既没有垃圾回收也没有内存溢出。 ^8-1293-1452
- ⏱ 2023-09-27 20:35:35
-
📌 在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致 ^8-1890-1937
- ⏱ 2023-09-27 20:37:07
第4章 虚拟机栈
-
📌 ,Java语言具有跨平台性,由于不同平台的CPU架构不同,所以Java的指令不能设计为基于寄存器的,而是设计为基于栈架构的。基于栈架构的优点是可以跨平台,指令集小,编译器容易实现。缺点是性能较低,实现同样的功能需要更多的指令。 ^9-846-959
- ⏱ 2023-12-20 20:20:13
-
📌 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。 ^9-6204-6239
- ⏱ 2023-12-20 20:22:15
-
📌 局部变量表也称为局部变量数组或本地变量表。局部变量表定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference)以及returnAddress类型。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。 ^9-9800-9953
- ⏱ 2023-12-20 20:36:25
-
📌 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。 ^9-10049-10136
- ⏱ 2023-12-20 20:47:03
-
📌 值得注意的是,在栈帧中,与性能调优关系最为密切的部分就是前面提到的局部变量表。在方法执行时,虚拟机使用局部变量表完成方法的传递。 ^9-15099-15163
- ⏱ 2024-03-07 15:15:56
-
📌 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。 ^9-15192-15240
- ⏱ 2024-03-07 15:16:15
-
📌 操作数栈也是栈帧中重要的内容之一,它主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。 ^9-15425-15478
- ⏱ 2023-12-20 20:44:39
-
📌 操作数栈在方法执行过程中,根据字节码指令往栈中写入数据或提取数据,即入栈(push)/出栈(pop)。 ^9-15507-15558
- ⏱ 2024-03-07 15:17:15
-
📌 操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈(push)和出栈(pop)操作来完成一次数据访问。如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新程序计数器中下一条需要执行的字节码指令。 ^9-16243-16363
- ⏱ 2023-12-20 20:46:13
-
📌 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证。另外,我们说JVM的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈,如代码清单4-9所示。 ^9-16392-16512
- ⏱ 2024-03-07 15:19:32
-
📌 静态链接是指方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的,一般称这样的方法为非虚方法。除去非虚方法的都叫作虚方法。一般来说,静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法。 ^9-23351-23457
- ⏱ 2023-12-21 19:26:48
第5章 本地方法接口
第6章 本地方法栈
第7章 堆
-
📌 大对象直接分配到老年代,在开发过程中应尽量避免程序中出现过多的大对象。 ^12-20704-20739
- ⏱ 2024-03-06 07:45:34
-
📌 )通过动态对象年龄判断,如果Survivor区中相同年龄的所有对象的大小总和大于Survivor区的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。 ^12-20816-20923
- ⏱ 2024-03-06 07:46:54
-
📌 程序中所有的线程共享Java中的堆区域,但是堆中还有一部分区域是线程私有,这部分区域称为线程本地分配缓存区(Thread Local Allocation Buffer,TLAB)。TLAB表示JVM为每个线程分配了一个私有缓存区域,这块缓存区域包含在Eden区内。简单说TLAB就是在堆内存中的Eden区分配了一块线程私有的内存区域。什么是TLAB呢? ^12-21938-22144
- ⏱ 2024-03-06 07:48:01
-
📌 下面解说几个常用的参数设置。-XX:+PrintFlagsInitial:查看所有的参数的默认初始值。-XX:+PrintFlagsFinal:查看所有的参数的最终值(可能会存在修改,不再是初始值)。-Xms:初始堆空间内存(默认为物理内存的1/64)。-Xmx:最大堆空间内存(默认为物理内存的1/4)。-Xmn:设置新生代的大小(初始值及最大值)。-XX:NewRatio:配置新生代与老年代在堆结构的占比。-XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例。-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄。-XX:+PrintGCDetails:输出详细的GC处理日志。打印GC简要信息:① -XX:+PrintGC;② -verbose:gc。-XX:HandlePromotionFailure:是否设置空间分配担保,在发生Minor GC之前,JVM会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。如果大于,则此次Minor GC是安全的;如果小于,则JVM会查看-XX:HandlePromotionFailure设置值是否允许担保失败。 ^12-24391-25097
- ⏱ 2024-03-06 07:53:44
-
📌 在JVM中,对象是在Java堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法,那么就可能被优化成栈上分配。这样就无须在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。 ^12-26084-26224
- ⏱ 2024-03-06 07:57:38
-
📌 一般在开发中能使用局部变量的,就不要使用在方法外定义 ^12-28625-28651
- ⏱ 2024-03-06 08:01:36
-
📌 使用逃逸分析,编译器可以对程序做如下优化。(1)栈上分配。将堆分配转化为栈分配。针对那些作用域不会逃逸出方法的对象,在分配内存时不再将对象分配在堆内存中,而是将对象分配在栈上,这样,随着方法的调用结束,栈空间的回收也会回收掉分配到栈上的对象,不再给垃圾收集器增加额外的负担,从而提升应用程序整体性能。(2)同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。(3)分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存中,而是存储在栈中。 ^12-28752-29110
- ⏱ 2024-03-06 08:04:37
-
📌 线程同步的代价是相当高的,同步的后果是降低了并发性和性能。在动态编译同步块的时候,JIT编译器可以借助逃逸分析,来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。如果没有,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步,这样就能大大提高并发性和性能。这个取消同步的过程就叫同步省略,也叫锁消除。代码清单7-17展示了同步省略效果。 ^12-31181-31367
- ⏱ 2024-03-06 08:07:59
第8章 方法区
-
📌 方法区与传统语言中的编译代码存储区或者操作系统进程的正文段的作用非常类似,它存储了每一个类的结构信息,例如,运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。 ^13-2749-2882
- ⏱ 2024-03-07 11:46:44
-
📌 (3)方法区的大小跟堆空间一样,可以选择固定大小或者可扩展。方法区的大小决定了系统可以保存多少个类。如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误,如java.lang.OutOfMemoryError:PermGen space或者java.lang.OutOfMemoryError:Metaspace。 ^13-3817-3982
- ⏱ 2024-03-07 11:47:55
-
📌 而BEA JRockit和IBM J9虚拟机是在本地内存中实现的方法区,只要没有触碰到进程可用的内存上限就不会出问题 ^13-4971-5029
- ⏱ 2024-03-07 11:54:35
-
📌 借鉴BEA JRockit虚拟机对于方法区的实现,HotSpot虚拟机在JDK 8也完全废弃了永久代的概念,取而代之的是在本地内存中实现的元空间(Metaspace),图8-8和图8-9分别说明了不同版本的JDK对方法区的描述,JDK 7及其之前的方法区一般称为永久代,JDK 8之后称为元空间。 ^13-5030-5178
- ⏱ 2024-03-07 11:54:51
-
📌 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于元空间不在虚拟机设置的内存中,而是在本地内存。另外,永久代、元空间二者并不只是名字变了,内部结构也调整了,稍后会做介绍。 ^13-5753-5858
- ⏱ 2024-03-07 11:55:29
-
📌 根据Java虚拟机规范的规定,如果方法区无法满足新的内存分配需求,将抛出OOM异常。 ^13-5887-5929
- ⏱ 2024-03-07 11:55:39
-
📌 假设-XX:MetaspaceSize默认值为20MB,这是初始的高水位线,一旦方法区内存使用触及这个水位线,Full GC将会被触发并卸载没用的类(包括这些类对应的类加载器也不再存活)。垃圾收集后,高水位标记可能会根据类元数据释放的空间量自动提高或降低,如果释放的空间很少,那么在不超过MaxMetaspaceSize时,该值会被提高,以免过早引发下一次垃圾收集。如果释放空间过多,那么该值会被降低。如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full GC多次调用。为了避免频繁GC,建议将-XX:MetaspaceSize设置为一个相对较高的值。 ^13-7090-7391
- ⏱ 2024-03-07 11:59:38
-
📌 所以要弄清楚方法区的运行时常量池,需要理解class文件中的常量池 ^13-16061-16094
- ⏱ 2024-03-07 12:57:25
-
📌 一个Java应用程序中所包含的所有Java类的常量池组成了JVM中的大的运行时常量池。 ^13-16095-16138
- ⏱ 2024-03-07 12:57:19
-
📌 虚拟机加载类或接口后,就会创建对应的运行时常量池。JVM为每个已加载的类型(类或接口)都维护一个常量池。池中的数据项像数组项一样,是通过索引访问的。 ^13-18714-18788
- ⏱ 2024-03-07 13:01:32
-
📌 运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址。 ^13-18817-18901
- ⏱ 2024-03-07 13:01:49
-
📌 运行时常量池相对于class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。 ^13-18930-19081
- ⏱ 2024-03-07 13:02:51
第9章 对象的实例化内存布局与访问定位
第10章 直接内存
第11章 执行引擎
第12章 字符串常量池
第2篇 垃圾收集篇
第14章 垃圾收集相关算法
- 📌 在Java语言中,GC Roots集合中的对象引用包括以下几种类型。虚拟机栈中对象的引用,比如,各个线程被调用的方法中使用到的引用数据类型的参数、局部变量等。本地方法栈内JNI(本地方法)对象的引用。方法区中引用数据类型的静态变量。方法区中常量对象的引用,比如字符串常量池(String Table)里的引用。所有被同步锁synchronized持有的对象引用。JVM内部的引用。基本数据类型对应的Class对象引用,一些常驻的异常对象引用(如NullPointerException、OutOfMemoryError),系统类加载器对象引用等。反映JVM内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存对象的引用等。 ^19-4226-4686
- ⏱ 2024-03-07 11:33:12
第15章 垃圾收集相关概念
第16章 垃圾收集器
-
📌 并且Parallel GC在JDK6之后成为HotSpot默认GC ^21-3941-3974
- ⏱ 2024-03-06 22:47:27
-
📌 但是在2020年3月发布的JDK14中,CMS垃圾收集器被彻底删除了。 ^21-4093-4128
- ⏱ 2024-03-06 22:47:56
-
📌 随着G1 GC的出现,GC从传统的连续堆内存布局设计,逐渐走向不连续内存块,这是通过引入Region概念实现,也就是说,由一堆不连续的Region组成了堆内存。其实也不能说是不连续的,只是它从传统的物理连续逐渐改变为逻辑上的连续,这是通过Region的动态分配方式实现的,我们可以把一个Region分配给Eden、Survivor、老年代、大对象区间、空闲区间等的任意一个,而不是固定它的作用,因为越是固定,越是呆板。到2017年JDK9中,G1变成了默认的垃圾收集器,替代了CMS。2018年3月发布的JDK10中,G1垃圾收集器已经可以并行完整垃圾回收了,G1实现并行性来改善最坏情况下的延迟。之后在JDK12,继续增强G1,自动返回未用堆内存给操作系统。 ^21-4217-4547
- ⏱ 2024-03-06 22:48:46
-
📌 不同区域的对象,采取不同的收集方式,以便提高回收效率。因此根据垃圾收集器工作的内存区间不同,可分为新生代垃圾收集器、老年代垃圾收集器和整堆垃圾收集器,如图16-3所示。 ^21-5126-5210
- ⏱ 2024-03-06 23:04:28
-
📌 并行的多个任务之间是不互相抢占CPU资源的。而且只有在多CPU或者一个CPU多核的情况中,才会发生并行,否则,看似同时发生的事情,其实都是并发执行的。 ^21-7464-7539
- ⏱ 2024-03-06 23:09:11
-
📌 在程序吞吐量优先的应用场景中,Parallel Scavenge收集器和Parallel Old收集器的组合,在Server模式下的内存回收性能很不错 ^21-16075-16150
- ⏱ 2024-03-06 23:21:51
-
📌 CMS(Concurrent Low Pause Collector)是JDK1.4.2开始引入的新GC算法,在JDK5和JDK6中得到了进一步改进,它的主要适合场景是对响应时间的需求大于对吞吐量的要求。CMS垃圾收集器在强交互应用中几乎可认为有划时代意义。它是HotSpot虚拟机中第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程同时工作。 ^21-18340-18520
- ⏱ 2024-03-06 23:29:13
-
📌 目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望减少系统停顿时间,以给用户带来较好的使用体验。CMS收集器就非常符合这类应用的需求。 ^21-18656-18750
- ⏱ 2024-03-07 07:07:22
-
📌 初始标记(Initial-Mark)阶段:在这个阶段中,程序中所有的工作线程都将会因为STW机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象 ^21-19115-19205
- ⏱ 2024-03-07 07:08:58
-
📌 尽管CMS收集器采用的是并发回收,但是在其初始化标记和再次标记这两个阶段中仍然需要执行STW机制暂停程序中的工作线程,不过停顿时间并不会太长,因此可以说明目前所有的垃圾收集器都做不到完全不需要STW,只是尽可能地缩短停顿时间。 ^21-19987-20100
- ⏱ 2024-03-07 07:10:44
-
📌 由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的。 ^21-20129-20170
- ⏱ 2024-03-07 07:11:04
-
📌 要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。 ^21-20349-20461
- ⏱ 2024-03-07 08:06:51
-
📌 -XX:+UseConcMarkSweepGC:指定使用CMS收集器执行内存回收任务。开启该参数后会自动将-XX:+UseParNewGC打开。即垃圾收集器组合为ParNew(Young区用)、CMS(Old区用)和Serial Old(CMS的备用方案)。 ^21-21845-21974
- ⏱ 2024-03-07 08:08:21
-
📌 G1是一款基于并行和并发的收集器,它把堆内存分割为很多区域(Region),它们虽然物理上不连续,但是逻辑上是连续的。然后使用不同的Region来表示Eden区、Survivor 0区、Survivor 1区、老年代等。 ^21-24423-24533
- ⏱ 2024-03-06 23:36:46
-
📌 [插图] ^21-41349-41350
- ⏱ 2024-03-06 23:43:12
-
📌 Java垃圾收集器的配置对于JVM优化来说是很重要的,选择合适的垃圾收集器可以让JVM的性能有一个很大的提升。怎么选择垃圾收集器呢?我们可以参考下面的选择标准。优先让JVM自适应调整堆的大小。如果内存小于100M,使用串行收集器。如果是单核、单机程序,并且没有停顿时间的要求,串行收集器。如果是多CPU、需要高吞吐量、允许停顿时间超过1s,选择并行或者JVM自己选择。如果是多CPU、追求低停顿时间,需快速响应(比如延迟不能超过1s,如互联网应用),使用并发收集器。最后需要明确一个观点,没有最好的收集器,更没有万能的收集器。调优永远是针对特定场景、特定需求,不存在一劳永逸的收集器。 ^21-41723-42164
- ⏱ 2024-03-06 23:43:50
第3篇 字节码与类的加载篇
-
📌 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。对于字符串,则使用u1数组进行表示。 ^22-8590-8703
- ⏱ 2023-12-26 07:57:53
-
📌 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。 ^22-8732-8780
- ⏱ 2023-12-26 07:58:24
第18章 字节码指令集与解析
第19章 类的加载过程详解
第20章 类加载器
-
📌 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确认其在JVM中的唯一性 ^25-1802-1842
- ⏱ 2024-03-06 17:19:15
-
📌 通常类加载机制有三个基本特征,分别是双亲委派模型、可见性和单一性。1 双亲委派模型如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。详细讲解见20.6节。2 可见性子类加载器可以访问父类加载器加载的类型,但是反过来是不允许的。不然,因为缺少必要的隔离,就没有办法利用类加载器去实现容器的逻辑。3 单一性由于父类加载器的类型对于子类加载器是可见的,所以父类加载器中加载过的类型,就不会在子加载器中重复加载。但是注意,同一个类仍然可以被同级别的类加载器加载多次,因为互相并不可见。 ^25-2086-2648
- ⏱ 2024-03-06 17:23:29
-
📌 在做Java类型转换时,只有两个类型都是由同一个加载器所加载,才能进行类型转换,否则转换时会发生异常。 ^25-9101-9152
- ⏱ 2024-03-06 17:44:39
-
📌 数组类的Class对象,不是由类加载器创建的,而是在Java运行期JVM根据需要自动创建的。数组类的类加载器可以通过Class.getClassLoader()方法返回,如果数组元素是引用数据类型,类加载器与数组当中元素类型相同,如果数组元素类型是基本数据类型,数组类没有类加载器,如代码清单20-6所示。 ^25-10421-10574
- ⏱ 2024-03-06 17:46:51
-
📌 检查类加载的委派过程是否为单向的,这个方式虽然从结构上说比较清晰,使各个类加载器的职责非常明确,但同时会带来一个问题,即顶层的类加载器无法访问底层的类加载器所加载的类。 ^25-25050-25134
- ⏱ 2024-03-06 18:21:32
-
📌 通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器加载的类为应用类。按照这种模式,应用类访问系统类自然是没有问题,但是系统类访问应用类就会出现问题。比如在系统类中提供了一个接口,该接口需要在应用类中实现,该接口还绑定一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。所以Java虚拟机规范并没有明确要求类加载器的加载机制一定要使用双亲委派模型,只是建议采用这种方式而已,比如在Tomcat中,类加载器所采用的加载机制就和传统的双亲委派模型有一定区别,当缺省的类加载器接收到一个类的加载任务时,首先会由它自行加载,当它加载失败时,才会将类的加载任务委派给它的超类加载器去执行,这同时也是Servlet规范推荐的一种做法。 ^25-25163-25534
- ⏱ 2024-03-06 18:24:52
第4篇 性能监控与调优篇
-
📌 jstat还可以用来判断是否出现内存泄漏,步骤如下。(1)在长时间运行的Java程序中,可以运行jstat命令连续获取多行性能数据,并取这几行数据中OU列(即已占用的老年代内存)的最小值。(2)每隔一段较长的时间重复一次上述操作,获得多组OU最小值。如果这些值呈上涨趋势,则说明该Java程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。 ^26-8263-8513
- ⏱ 2024-03-03 10:37:32
-
📌 jinfo(Configuration Info for Java)可用于查看和调整JVM的配置参数 ^26-8623-8673
- ⏱ 2024-03-03 10:38:51
-
📌 其中选项-dump、-heap、-histo是开发人员在工作中使用频率较高的指令。 ^26-18703-18744
- ⏱ 2024-03-03 10:48:37
-
📌 通常在写dump文件前会触发一次Full GC,所以dump文件里保存的都是Full GC后留下的对象信息 ^26-19048-19101
- ⏱ 2024-03-03 10:49:27
-
📌 当程序发生内存溢出退出系统时,一些瞬时信息都随着程序的终止而消失,而重现OOM问题往往比较困难或者耗时。若能在OOM时,自动导出dump文件就显得非常迫切。可以配置JVM参数“-XX:+HeapDumpOnOutOfMemoryError:”使程序发生OOM时,导出应用程序的当前堆快照。 ^26-20718-20862
- ⏱ 2024-03-03 10:51:18
-
📌 jhat命令在JDK9中已经被删除,官方建议用VisualVM代替。实际工作中一般不会直接在生产服务器使用jhat分析dump文件。 ^26-24309-24375
- ⏱ 2024-03-03 10:54:28
-
📌 生成线程快照的作用是可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题,这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。 ^26-27204-27313
- ⏱ 2024-03-03 11:02:46