用sql2000做网站,wap浏览器网页版,WordPress是静态吗,用花生壳做网站目录 类实例化加载顺序
类的实例化顺序
JVM创建对象的过程
JVM的运行机制
直接内存#xff08;Direct Memory#xff09;
JVM后台运行的线程
JVM 常用参数
标准参数中比较有用的#xff1a;
非标准参数又称为扩展参数#xff0c;比较有用的是
非Stable参数 class初…目录 类实例化加载顺序
类的实例化顺序
JVM创建对象的过程
JVM的运行机制
直接内存Direct Memory
JVM后台运行的线程
JVM 常用参数
标准参数中比较有用的
非标准参数又称为扩展参数比较有用的是
非Stable参数 class初始化过程是什么
JVM内存模型如何分配的
JVM的类加载阶段 JVM结构
JVM性能调优的原则有哪些
什么情况下需要JVM调优
在JVM调优时你关注哪些指标
JVM常用参数有哪些
JVM常用性能调优工具有哪些
线上排查问题的一般流程是怎么样的
什么情况下会抛出OOM呢
系统OOM之前都有哪些现象
如何进行堆Dump文件分析
如何进行GC日志分析
线上死锁是如何排查的
线上YGC耗时过长优化方案有哪些
线上频繁FullGC优化方案有哪些
内存逃逸分析
如何进行线上堆外内存泄漏的分析Netty尤其居多
线上元空间内存泄露优化方案有哪些
java类加载器有哪些
双亲委派机制是什么
双亲委派机制
如何破坏双亲委派模型
如果基础类又要调用用户的代码
GC如何判断对象可以被回收
那么GcRoot有哪些
回收机制是
如何回收内存对象有哪些回收算法
jvm有哪些垃圾回收器实际中如何选择
JVM8为什么要增加元空间
JVM8中元空间有哪些特点
如何解决线上gc频繁的问题
内存溢出的原因有哪些如何排查线上问题 类实例化加载顺序 加载当程序访问某个类时JVM会首先检查该类是否已经加载到内存中。如果尚未加载则会进行加载操作。加载操作将类的字节码文件加载到内存并为其创建一个Class对象。 连接验证、准备、解析 验证验证阶段会对类的字节码进行验证确保它的结构正确且满足Java虚拟机规范。 准备准备阶段会为类的静态变量分配内存并设置初始值这些变量会在类初始化时赋予其真正的初始值。 解析解析阶段会将符号引用转换为直接引用建立虚拟机内部的数据结构以便后续的访问。 初始化初始化阶段将执行类的初始化代码包括静态变量的赋值和静态代码块的执行。类的初始化是按需进行的即在首次使用该类或创建该类的实例时进行。
对于类的实例化顺序可以按照以下规则进行 静态变量和静态代码块按照在类中的顺序依次执行且仅执行一次。 实例变量和实例代码块按照在类中的顺序依次执行在每次创建实例时都会执行一次。 构造方法最后执行用于初始化实例的状态。
类的实例化顺序 JVM创建对象的过程 类加载当程序首次使用某个类时JVM会先进行类的加载。类加载是将类的字节码文件加载到内存并创建一个对应的Class对象。 分配内存在类加载完成后JVM会根据类的内部结构在堆内存中分配对象所需的内存空间。分配的方式可以是指针碰撞Bump the Pointer或空闲列表Free List。 初始化零值JVM会将分配的内存空间初始化为零值包括基本类型的零值和引用类型的null。 设置对象头JVM会设置对象的头部信息包括存储对象的哈希码、锁状态、GC标记等。 执行构造函数JVM会执行类的构造函数进行对象的初始化。构造函数负责对对象的属性进行初始化并可以执行其他必要的操作。 对象创建完成经过以上步骤JVM成功创建了一个对象并将对象的引用返回给程序。
JVM的运行机制
JVMJava Virtual Machine是用于运行Java字节码的虚拟机包括一套字节码指令集、一组程序寄存器、一个虚拟机栈、一个虚拟机堆、一个方法区和一个垃圾回收器。JVM运行在操作系统之上不与硬件设备直接交互。Java源文件在通过编译器之后被编译成相应的Class文件字节码文件.Class文件又被JVM中的解释器编译成机器码在不同的操作系统Windows、Linux、Mac上运行。每种操作系统的解释器都是不同的但基于解释器实现的虚拟机是相同的这也是Java能够跨平台的原因。在一个Java进程开始运行后虚拟机就开始实例化了有多个进程启动就会实例化多个虚拟机实例。进程退出或者关闭则虚拟机实例消亡在多个虚拟机实例之间不能共享数据。Java程序的具体运行过程如下。
1Java源文件被编译器编译成字节码文件。
2JVM将字节码文件编译成相应操作系统的机器码。
3机器码调用相应操作系统的本地方法库执行相应的方法 类加载器子系统用于将编译好的Class文件加载到JVM中
◎ 运行时数据区用于存储在JVM运行过程中产生的数据包括程序计数器、方法区、本地方法区、虚拟机栈和虚拟机堆
◎ 执行引擎包括即时编译器和垃圾回收器即时编译器用于将Java字节码编译成具体的机器码垃圾回收器用于回收在运行过程中不再使用的对象
◎ 本地接口库用于调用操作系统的本地方法库完成具体的指令操作。
直接内存Direct Memory
并不是虚拟机运行时数据区的一部分也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用而且也可能导致OutOfMemoryError异常出现
。
在JDK 1.4中新加入了NIONew Input/Output类引入了一种基于通道Channel与缓冲区Buffer的I/O方式它可以使用Native函数库直接分配堆外内存然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能
因为避免了在Java堆和Native堆中来回复制数据。
显然本机直接内存的分配不会受到Java堆大小的限制但是既然是内存肯定还是会受到本机总内存包括RAM以及SWAP区或者分页文件大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时会根据实际内存设置-Xmx等参数信息但经常忽略直接内存使得各个内存区域总和大于物理内存限制包括物理的和操作系统级的限制从而导致动态扩展时出现OutOfMemoryError异常。
JVM后台运行的线程
在JVM后台会运行许多线程其中一些是JVM自己创建和管理的线程用于支持Java程序的执行和JVM的运行。以下是一些常见的JVM后台运行的线程 主线程Main Thread Java程序的入口点是主线程程序从main方法开始执行主线程负责启动和执行Java程序的其他线程。 垃圾回收线程Garbage Collection Threads 垃圾回收线程是JVM中负责执行垃圾回收的线程。它们扫描堆内存标记和清理不再使用的对象从而释放内存空间。 编译线程Compilation Threads 编译线程负责将Java源代码编译成可执行的字节码。JVM中的即时编译器将字节码编译为机器码以提高运行效率。 信号分发线程Signal Dispatcher Thread 信号分发线程负责接收和分发操作系统发送的信号如崩溃信号、用户自定义信号等。 定时器线程Timer Threads 定时器线程负责调度和执行定时任务它在后台运行监视计划执行的任务并在指定的时间触发相应的操作。
除了上述线程之外还有一些其他的线程用于执行特定的功能例如监控线程、虚拟机内部线程等。这些线程都运行在JVM后台对于应用程序来说是透明的由JVM负责创建、调度和管理。
JVM 常用参数
标准参数中比较有用的
verbose -verbose:class 输出jvm载入类的相关信息当jvm报告说找不到类或者类冲突时可此进行诊断。 -verbose:gc 输出每次GC的相关情况。 -verbose:jni 输出native方法调用的相关情况一般用于诊断jni调用错误信息。
非标准参数又称为扩展参数比较有用的是
-Xms512m 设置JVM促使内存为512m。此值可以设置与-Xmx相同以避免每次垃圾回收完成后JVM重新分配内存。
-Xmx512m 设置JVM最大可用内存为512M。
-Xmn200m设置年轻代大小为200M。整个堆大小年轻代大小 年老代大小 持久代大小。持久代一般固定大小为64m所以增大年轻代后将会减小年老代大小。此值对系统性能影响较大Sun官方推荐配置为整个堆的3/8。
-Xss128k
设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的不能无限生成经验值在3000~5000左右。
-Xloggc:file 与-verbose:gc功能类似只是将每次GC事件的相关情况记录到一个文件中文件的位置最好在本地以避免网络的潜在问题。 若与verbose命令同时出现在命令行中则以-Xloggc为准。 -Xprof
跟踪正运行的程序并将跟踪数据在标准输出输出适合于开发环境调试。
非Stable参数
用-XX作为前缀的参数列表在jvm中可能是不健壮的SUN也不推荐使用后续可能会在没有通知的情况下就直接取消了但是由于这些参数中的确有很多是对我们很有用的比如我们经常会见到的-XX:PermSize、-XX:MaxPermSize等等
首先来介绍行为参数
参数及其默认值描述-XX:-DisableExplicitGC禁止调用System.gc()但jvm的gc仍然有效-XX:MaxFDLimit最大化文件描述符的数量限制-XX:ScavengeBeforeFullGC新生代GC优先于Full GC执行-XX:UseGCOverheadLimit在抛出OOM之前限制jvm耗费在GC上的时间比例-XX:-UseConcMarkSweepGC对老生代采用并发标记交换算法进行**GC**-XX:-UseParallelGC启用并行**GC**-XX:-UseParallelOldGC对Full GC启用并行当-XX:-UseParallelGC启用时该项自动启用-XX:-UseSerialGC启用串行**GC**-XX:UseThreadPriorities启用本地线程优先级 上面表格中黑体的三个参数代表着jvm中GC执行的三种方式即串行、并行、并发 串行**SerialGC是jvm的默认GC方式一般适用于小型应用和单处理器算法比较简单GC效率也较高但可能会给应用带来停顿 并行ParallelGC是指GC运行时对应用程序运行没有影响GC和app两者的线程在并发执行这样可以最大限度不影响app的运行 并发ConcMarkSweepGC**是指多个线程并发执行GC一般适用于多处理器系统中可以提高GC的效率但算法复杂系统消耗较大 性能调优参数列表 参数及其默认值描述-XX:LargePageSizeInBytes4m设置用于Java堆的大页面尺寸-XX:MaxHeapFreeRatio70GC后java堆中空闲量占的最大比例-XX:MaxNewSizesize新生成对象能占用内存的最大值-XX:MaxPermSize64m老生代对象能占用内存的最大值-XX:MinHeapFreeRatio40GC后java堆中空闲量占的最小比例-XX:NewRatio2新生代内存容量与老生代内存容量的比例-XX:NewSize2.125m新生代对象生成时占用内存的默认值-XX:ReservedCodeCacheSize32m保留代码占用的内存容量-XX:ThreadStackSize512设置线程栈大小若为0则使用系统默认值-XX:UseLargePages使用大页面内存 我们在日常性能调优中基本上都会用到以上黑体的这几个属性 调试参数列表 参数及其默认值描述-XX:-CITime打印消耗在JIT编译的时间-XX:ErrorFile./hs_err_pidpid.log保存错误日志或者数据到文件中-XX:-ExtendedDTraceProbes开启solaris特有的dtrace探针-XX:HeapDumpPath./java_pidpid.hprof指定导出堆信息时的路径或文件名-XX:-HeapDumpOnOutOfMemoryError当首次遭遇**OOM时导出此时堆中相关信息**-XX:OnErrorcmd args;cmd args出现致命ERROR之后运行自定义命令-XX:OnOutOfMemoryErrorcmd args;cmd args当首次遭遇OOM时执行自定义命令-XX:-PrintClassHistogram遇到Ctrl-Break后打印类实例的柱状信息与jmap -histo功能相同-XX:-PrintConcurrentLocks遇到**Ctrl-Break后打印并发锁的相关信息与jstack -l功能相同**-XX:-PrintCommandLineFlags打印在命令行中出现过的标记-XX:-PrintCompilation当一个方法被编译时打印相关信息-XX:-PrintGC每次GC时打印相关信息-XX:-PrintGC Details每次GC时打印详细信息-XX:-PrintGCTimeStamps打印每次GC的时间戳-XX:-TraceClassLoading跟踪类的加载信息-XX:-TraceClassLoadingPreorder跟踪被引用到的所有类的加载信息-XX:-TraceClassResolution跟踪常量池-XX:-TraceClassUnloading跟踪类的卸载信息-XX:-TraceLoaderConstraints跟踪类加载器约束的相关信息 class初始化过程是什么
首先类加载的机制过程分为5个部分加载、验证、准备、解析、初始化 类的初始化过程是指对类进行首次使用之前的准备和初始化操作。类的初始化包括以下几个步骤 加载Loading类的加载是指通过类加载器将类的字节码文件加载到内存中并创建一个对应的Class对象。加载过程包括查找类文件、将类文件字节码读取到内存并创建对应的Class对象。 链接Linking 验证Verification验证过程是对加载的类进行验证确保类的正确性和安全性包括验证类的格式、语义等方面。 准备Preparation准备阶段是为类的静态变量被static修饰的变量分配内存并进行默认初始化即赋予默认值比如0、null等。 解析Resolution解析过程是将符号引用转换为直接引用使得执行字节码时可以直接找到对应的目标。 初始化Initialization在初始化阶段执行类的初始化代码包括静态变量的显式赋值、静态代码块中的代码执行等。初始化阶段在Java虚拟机中是被加锁的确保只有一个线程对类进行初始化。
类的初始化是按需进行的只有在以下情况下才会触发类的初始化 创建类的实例对象 访问类的静态变量 调用类的静态方法 使用反射操作该类时
需要注意的是类的初始化是按照以上步骤依次进行的一旦开始了类的初始化就会按顺序依次执行各个步骤且仅进行一次。此外子类的初始化会触发父类的初始化过程。
值得一提的是类的加载和初始化过程是由Java虚拟机控制的开发人员在代码中无法直接干预类的加载和初始化顺序。
JVM内存模型如何分配的 JVM的类加载阶段 JVM结构 JVM性能调优的原则有哪些 多数的Java应用不需要在服务器上进行GC优化虚拟机内部已有很多优化来保证应用的稳定运行所以不要为了调优而调优不当的调优可能适得其反 在应用上线之前先考虑将机器的JVM参数设置到最优适合 在进行GC优化之前需要确认项目的架构和代码等已经没有优化空间。我们不能指望一个系统架构有缺陷或者代码层次优化没有穷尽的应用通过GC优化令其性能达到一个质的飞跃 GC优化是一个系统而复杂的工作没有万能的调优策略可以满足所有的性能指标。GC优化必须建立在我们深入理解各种垃圾回收器的基础上才能有事半功倍的效果 处理吞吐量和延迟问题时垃圾处理器能使用的内存越大即java堆空间越大垃圾收集效果越好应用运行也越流畅。这称之为GC内存最大化原则 在这三个属性吞吐量、延迟、内存中选择其中两个进行jvm调优称之为GC调优3选2 什么情况下需要JVM调优 Heap内存老年代持续上涨达到设置的最大内存值 Full GC 次数频繁 GC 停顿Stop World时间过长超过1秒具体值按应用场景而定 应用出现OutOfMemory 等内存异常 应用出现OutOfDirectMemoryError等内存异常 failed to allocate 16777216 byte(s) of direct memory (used: 1056964615, max: 1073741824) 应用中有使用本地缓存且占用大量内存空间 系统吞吐量与响应性能不高或下降 应用的CPU占用过高不下或内存占用过高不下 在JVM调优时你关注哪些指标 吞吐量用户代码时间 / 用户代码执行时间 垃圾回收时间。是评价垃圾收集器能力的重要指标之一是不考虑垃圾收集引起的停顿时间或内存消耗垃圾收集器能支撑应用程序达到的最高性能指标。吞吐量越高算法越好。 低延迟STW越短响应时间越好。评价垃圾收集器能力的重要指标度量标准是缩短由于垃圾收集引起的停顿时间或完全消除因垃圾收集所引起的停顿避免应用程序运行时发生抖动。暂停时间越短算法越好 在设计或使用GC 算法时我们必须确定我们的目标一个 GC 算法只可能针对两个目标之一即只专注于最大吞吐量或最小暂停时间或尝试找到一个二者的折衷 MinorGC尽可能多的收集垃圾对象。我们把这个称作MinorGC原则遵守这一原则可以降低应用程序FullGC 的发生频率。FullGC 较耗时是应用程序无法达到延迟要求或吞吐量的罪魁祸首 堆大小调整的着手点、分析点 统计Minor GC 持续时间 统计Minor GC 的次数 统计Full GC的最长持续时间 统计最差情况下Full GC频率 统计GC持续时间和频率对优化堆的大小是主要着手点 我们按照业务系统对延迟和吞吐量的需求在按照这些分析我们可以进行各个区大小的调整 一般来说吞吐量优先的垃圾回收器-XX:UseParallelGC -XX:UseParallelOldGC即常规的PS/PO 响应时间优先的垃圾回收器CMS、G1
JVM常用参数有哪些 Xms 是指设定程序启动时占用内存大小。一般来讲大点程序会启动的快一点但是也可能会导致机器暂时间变慢 Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存超出了这个设置值就会抛出OutOfMemory异常 Xss 是指设定每个线程的堆栈大小。这个就要依据你的程序看一个线程大约需要占用多少内存可能会有多少线程同时运行等 -Xmn、-XX:NewSize/-XX:MaxNewSize、-XX:NewRatio 高优先级-XX:NewSize/-XX:MaxNewSize 中优先级-Xmn默认等效 -Xmn-XX:NewSize-XX:MaxNewSize? 低优先级-XX:NewRatio 如果想在日志中追踪类加载与类卸载的情况可以使用启动参数 -XX:TraceClassLoading -XX:TraceClassUnloading JVM常用性能调优工具有哪些 MAT 提示可能的内存泄露的点 jvisualvm jconsole Arthas show-busy-java-threads https://github.com/oldratlee/useful-scripts/blob/master/docs/java.md#-show-busy-java-threads ####
线上排查问题的一般流程是怎么样的 CPU占用过高排查流程 利用 top 命令可以查出占 CPU 最高的的进程pid 如果pid为 9876 然后查看该进程下占用最高的线程id【top -Hp 9876】 假设占用率最高的线程 ID 为 6900将其转换为 16 进制形式 (因为 java native 线程以 16 进制形式输出) 【printf %x\n 6900】 利用 jstack 打印出 java 线程调用栈信息【jstack 9876 | grep 0x1af4 -A 50 --color】这样就可以更好定位问题 内存占用过高排查流程 查找进程id: 【top -d 2 -c】 查看JVM堆内存分配情况jmap -heap pid 查看占用内存比较多的对象 jmap -histo pid | head -n 100 查看占用内存比较多的存活对象 jmap -histo:live pid | head -n 100 什么情况下会抛出OOM呢 JVM98%的时间都花费在内存回收 每次回收的内存小于2%
满足这两个条件将触发OutOfMemoryException这将会留给系统一个微小的间隙以做一些Down之前的操作比如手动打印Heap Dump。并不是内存被耗空的时候才抛出
系统OOM之前都有哪些现象 每次垃圾回收的时间越来越长由之前的10ms延长到50ms左右FullGC的时间也有之前的0.5s延长到4、5s FullGC的次数越来越多最频繁时隔不到1分钟就进行一次FullGC 老年代的内存越来越大并且每次FullGC后老年代只有少量的内存被释放掉 如何进行堆Dump文件分析
可以通过指定启动参数 -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/usr/app/data/dump/heapdump.hpro 在发生OOM的时候自动导出Dump文件
如何进行GC日志分析
为了方便分析GC日志信息可以指定启动参数 【-Xloggc: app-gc.log -XX:PrintGCDetails -XX:PrintGCDateStamps】,方便详细地查看GC日志信息 使用 【jinfo pid】查看当前JVM堆的相关参数 继续使用 【jstat -gcutil 2315 1s 10】查看10s内当前堆的占用情况 也可以使用【jmap -heap pid】查看当前JVM堆的情况 我们可以继续使用 【jmap -F -histo pid | head -n 20】查看前20行打印即查看当前top20的大对象一般从这里可以发现一些异常的大对象如果没有那么可以继续排名前50的大对象分析 最后使用【jmap -F -dump:filea.bin pid】如果dump文件很大可以压缩一下【tar -czvf a.tar.gz a.bin】 再之后就是对dump文件进行分析了使用MAT分析内存泄露 参考案例 https://www.lagou.com/lgeduarticle/142372.html
线上死锁是如何排查的 jps 查找一个可能有问题的进程id 然后执行 【jstack -F 进程id】 如果环境允许远程连接JVM可以使用jconsole或者jvisualvm图形化界面检测是否存在死锁 线上YGC耗时过长优化方案有哪些 如果生命周期过长的对象越来越多比如全局变量或者静态变量等会导致标注和复制过程的耗时增加 对存活对象标注时间过长比如重载了Object类的Finalize方法导致标注Final Reference耗时过长或者String.intern方法使用不当导致YGC扫描StringTable时间过长。可以通过以下参数显示GC处理Reference的耗时-XX:PrintReferenceGC 长周期对象积累过多比如本地缓存使用不当积累了太多存活对象或者锁竞争严重导致线程阻塞局部变量的生命周期变长 案例参考 https://my.oschina.net/lishangzhi/blog/4703942
线上频繁FullGC优化方案有哪些 线上频繁FullGC一般会有这么几个特征 线上多个线程的CPU都超过了100%通过jstack命令可以看到这些线程主要是垃圾回收线程 通过jstat命令监控GC情况可以看到Full GC次数非常多并且次数在不断增加 排查流程 top找到cpu占用最高的一个 进程id 然后 【top -Hp 进程id】找到cpu占用最高的 线程id 【printf %x\n 线程id 】假设16进制结果为 a jstack 线程id | grep 0xa -A 50 --color 如果是正常的用户线程 则通过该线程的堆栈信息查看其具体是在哪处用户代码处运行比较消耗CPU 如果该线程是 VMThread则通过 jstat-gcutil命令监控当前系统的GC状况然后通过 jmapdump:formatb,file导出系统当前的内存数据。导出之后将内存情况放到eclipse的mat工具中进行分析即可得出内存中主要是什么对象比较消耗内存进而可以处理相关代码正常情况下会发现VM Thread指的就是垃圾回收的线程 再执行【jstat -gcutil 进程id】, 看到结果如果FGC的数量很高且在不断增长那么可以定位是由于内存溢出导致FullGC频繁系统缓慢 然后就可以Dump出内存日志然后使用MAT的工具分析哪些对象占用内存较大然后找到对象的创建位置处理即可 参考案例面试官如果你们的系统 CPU 突然飙升且 GC 频繁如何排查 #### 内存逃逸分析
1, 是JVM优化技术它不是直接优化手段而是为其它优化手段提供依据。
2逃逸分析主要就是分析对象的动态作用域。
3逃逸有两种方法逃逸和线程逃逸。 方法逃逸(对象逃出当前方法)当一个对象在方法里面被定义后它可能被外部方法所引用例如作为调用参数传递到其它方法中。
线程逃逸((对象逃出当前线程)这个对象甚至可能被其它线程访问到例如赋值给类变量或可以在其它线程中访问的实例变量
12345
4如果不存在逃逸则可以对这个变量进行优化
4.1. 栈上分配。 在一般应用中不会逃逸的局部对象占比很大如果使用栈上分配那大量对象会随着方法结束而自动销毁垃圾回收系统压力就小很多。
4.2. 同步消除 线程同步本身比较耗时如果确定一个变量不会逃逸出线程无法被其它线程访问到那这个变量的读写就不会存在竞争对这个变量的同步措施 可以清除。
4.3. 标量替换。 标量就是不可分割的量java中基本数据类型reference类型都是标量。相对的一个数据可以继续分解它就是聚合量aggregate。 如果把一个对象拆散将其成员变量恢复到基本类型来访问就叫做标量替换。 如果逃逸分析证明一个对象不会被外部访问并且这个对象可以被拆散的话那么程序真正执行的时候将可能不创建这个对象而改为直接在栈上创建若干个成员变量。
5逃逸分析还不成熟。 5.1不能保证逃逸分析的性能收益必定高于它的消耗。 判断一个对象是否逃逸耗时长如果分析完发现没有几个不逃逸的对象那时间就白白浪费了。 5.2基于逃逸分析的优化手段不成熟如上面提到的栈上分配由于hotspot目前的实现方式导致栈上分配实现起来复杂。
6,相关JVM参数 -XX:DoEscapeAnalysis 开启逃逸分析 -XX:PrintEscapeAnalysis 开启逃逸分析后可通过此参数查看分析结果。 -XX:EliminateAllocations 开启标量替换 -XX:EliminateLocks 开启同步消除 -XX:PrintEliminateAllocations 开启标量替换后查看标量替换情况。
如何进行线上堆外内存泄漏的分析Netty尤其居多 JVM的堆外内存泄露的定位一直是个比较棘手的问题 对外内存的泄漏分析一般都是先从堆内内存分析的过程中衍生出来的。有可能我们分析堆内内存泄露过程中发现我们计算出来的JVM堆内存竟然大于了整个JVM的Xmx的大小那说明多出来的是堆外内存 如果使用了 Netty 堆外内存那么可以自行监控堆外内存的使用情况不需要借助第三方工具我们是使用的“反射”拿到的堆外内存的情况 逐渐缩小范围直到 Bug 被找到。当我们确认某个线程的执行带来 Bug 时可单步执行可二分执行定位到某行代码之后跟到这段代码然后继续单步执行或者二分的方式来定位最终出 Bug 的代码。这个方法屡试不爽最后总能找到想要的 Bug 熟练掌握 idea 的调试让我们的“捉虫”速度快如闪电“闪电侠”就是这么来的。这里最常见的调试方式是预执行表达式以及通过线程调用栈死盯某个对象就能够掌握这个对象的定义、赋值之类 在使用直接内存的项目中最好建议配置 -XX:MaxDirectMemorySize设定一个系统实际可达的最大的直接内存的值默认的最大直接内存大小等于 -Xmx的值 排查堆外泄露建议指定启动参数 -XX:NativeMemoryTrackingsummary - Dio.netty.leakDetection.targetRecords100-Dio.netty.leakDetection.levelPARANOID后面两个参数是Netty的相关内存泄露检测的级别与采样级别 参考案例 Netty堆外内存泄露排查盛宴 - 美团技术团队 线上元空间内存泄露优化方案有哪些 需要注意的一点是 Java8以及Java8的JVM已经将永久代废弃了取而代之的是元空间且元空间是不是在JVM堆中的而属于堆外内存受最大物理内存限制。最佳实践就是我们在启动参数中最好设置上 -XX:MetaspaceSize1024m -XX:MaxMetaspaceSize1024m。具体的值根据情况设置。为避免动态申请可以直接都设置为最大值 元空间主要存放的是类元数据而且metaspace判断类元数据是否可以回收是根据加载这些类元数据的Classloader是否可以回收来判断的只要Classloader不能回收通过其加载的类元数据就不会被回收。所以线上有时候会出现一种问题由于框架中往往大量采用类似ASM、javassist等工具进行字节码增强生成代理类。如果项目中由主线程频繁生成动态代理类那么就会导致元空间迅速占满无法回收 具体案例可以参见 一次完整的JVM堆外内存泄漏故障排查记录 - 知乎
java类加载器有哪些
Bootstrap类加载器
启动类加载器主要加载的是JVM自身需要的类这个类加载使用C语言实现的没有父类是虚拟机自身的一部分它负责将 JAVA_HOME/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中注意必由于虚拟机是按照文件名识别加载jar包的如rt.jar如果文件名不被虚拟机识别即使把jar包丢到lib目录下也是没有作用的(出于安全考虑Bootstrap启动类加载器只加载包名为java、javax、sun等开头
Extention 类加载器
扩展类加载器是指Sun公司实现的sun.misc.Launcher$ExtClassLoader类由Java语言实现的父类加载器为null是Launcher的静态内部类它负责加载JAVA_HOME/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库开发者可以直接使用标准扩展类加载器 [
](深入理解Java类加载器(ClassLoader)_java classloader-CSDN博客)
Application类加载器
称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。父类加载器为ExtClassLoader它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库也就是我们经常用到的classpath路径开发者可以直接使用系统类加载器一般情况下该类加载是程序中默认的类加载器通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器
Custom自定义类加载器
应用程序可以自定义类加载器父类加载器为AppClassLoader 双亲委派机制是什么 双亲委派机制
双亲委派机制Parent Delegation Model是Java类加载器的一种工作机制。它定义了类加载器的层次结构和类加载的行为保证Java类的安全性和一致性。
在Java中类加载器ClassLoader根据特定的规则来加载类的字节码并将其转换为可执行的Java类。双亲委派机制是指当类加载器在加载某个类时首先将该请求委派给父类加载器来尝试加载。只有当父类加载器无法加载时才由当前类加载器来尝试加载。这样的委派关系一直往上追溯直到达到最顶层的启动类加载器。
双亲委派机制的优势在于保证了类的统一性和安全性。当一个类加载器尝试加载某个类时它会先检查是否被父类加载器加载过。如果父类加载器已经加载了该类那么直接返回已加载的类确保了类的唯一性。同时通过这种机制可以防止恶意代码通过自定义的类加载器来替换Java核心库中的类提高了系统的安全性。
总结来说双亲委派机制采用了一种层次式的类加载器结构它通过逐级委派的方式保证类加载的一致性和安全性。在使用Java类加载器时了解双亲委派机制的工作原理对于理解类加载的过程和解决类加载问题非常有帮助。 如何破坏双亲委派模型
一双亲委派模型的第一次“被破坏”是重写自定义加载器的loadClass(),jdk不推荐。一般都只是重写findClass()这样可以保持双亲委派机制.而loadClass方法加载规则由自己定义就可以随心所欲的加载类
双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前——即JDK 1.2发布之前。由于双亲委派模型在JDK 1.2之后才被引入而类加载器和抽象类java.lang. ClassLoader则在JDK 1.0时代就已经存在面对已经存在的用户自定义类加载器的实现代码Java设计者引入双亲委派模型时不得不做出一些妥协。为了向前兼容JDK 1.2之后的java.lang.ClassLoader添加了一个新的protected方法findClass()在此之前用户去继承java. lang.ClassLoader的唯一目的就是为了重写loadClass()方法因为虚拟机在进行类加载的时候会调用加载器的私有方法loadClassInternal()而这个方法的唯一逻辑就是去调用自己的loadClass()。 二双亲委派模型的第二次“被破坏”是ServiceLoader和Thread.setContextClassLoader()。即线程上下文类加载器contextClassLoader。双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载)基础类之所以被称为“基础”是因为它们总是作为被调用代码调用的API。但是
如果基础类又要调用用户的代码
那该怎么办呢线程上下文类加载器就出现了。 SPI。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置如果创建线程时还未设置它将会从父线程中继承一个如果在应用程序的全局范围内都没有设置过那么这个类加载器默认就是应用程序类加载器。了有线程上下文类加载器JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码也就是父类加载器请求子类加载器去完成类加载动作这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器已经违背了双亲委派模型但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式例如JNDI,JDBC,JCE,JAXB和JBI等。 线程上下文类加载器默认情况下就是AppClassLoader那为什么不直接通过getSystemClassLoader()获取类加载器来加载classpath路径下的类的呢其实是可行的但这种直接使用getSystemClassLoader()方法获取AppClassLoader加载类有一个缺点那就是代码部署到不同服务时会出现问题如把代码部署到Java Web应用服务或者EJB之类的服务将会出问题因为这些服务使用的线程上下文类加载器并非AppClassLoader而是Java Web应用服自家的类加载器类加载器不同。所以我们应用该少用getSystemClassLoader()。总之不同的服务使用的可能默认ClassLoader是不同的但使用线程上下文类加载器总能获取到与当前程序执行相同的ClassLoader从而避免不必要的问题
三双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求导致的这里所说的“动态性”指的是当前一些非常“热门”的名词代码热替换、模块热部署等简答的说就是机器不用重启只要部署上就能用。
OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块OSGi中称为Bundle都有一个自己的类加载器当需要更换一个Bundle时就把Bundle连同类加载器一起换掉以实现代码的热替换。
GC如何判断对象可以被回收 垃圾收集器Garbage Collector会在运行时自动判断对象是否可以被回收。下面是一些常见的对象回收判断策略 引用计数法Reference Counting每个对象都有一个引用计数器当有一个新的引用指向对象时计数器加1当引用失效时计数器减1。当计数器为0时表示对象不再被引用可以被回收。但引用计数法无法解决循环引用的情况。 可达性分析算法Reachability AnalysisJava虚拟机使用可达性分析算法来判断对象是否可能被程序引用。从GC Roots如被活动线程栈引用的对象、静态变量引用的对象等开始通过对象之间的引用关系进行遍历无法到达的对象即为不可达对象可以被回收。
那么GcRoot有哪些 虚拟机栈中引用的对象 方法区中静态变量引用的对象。 方法区中常量引用的对象 本地方法栈中即一般说的native方法引用的对象 回收机制是 强引用通过关键字new的对象就是强引用对象强引用指向的对象任何时候都不会被回收宁愿OOM也不会回收。 软引用如果一个对象持有软引用那么当JVM堆空间不足时会被回收。一个类的软引用可以通过java.lang.ref.SoftReference持有。 弱引用如果一个对象持有弱引用那么在GC时只要发现弱引用对象就会被回收。一个类的弱引用可以通过java.lang.ref.WeakReference持有。 虚引用几乎和没有一样随时可以被回收。通过PhantomReference持有。 如何回收内存对象有哪些回收算法
1.标记-清除Mark-Sweep算法
分为标记和清除两个阶段首先标记出所有需要回收的对象在标记完成后统一回收所有被标记的对象。 它的主要不足有两个 效率问题标记和清除两个过程的效率都不高。 空间问题标记清除之后会产生大量不连续的内存碎片空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 复制算法
为了解决效率问题一种称为复制Copying的收集算法出现了它将可用内存按容量划分为大小相等的两块每次只使用其中的一块。当这一块的内存用完了就将还存活着的对象复制到另外一块上面然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收内存分配时也就不用考虑内存碎片等复杂情况只要移动堆顶指针按顺序分配内存即可实现简单运行高效。 复制算法的代价是将内存缩小为了原来的一半减少了实际可用的内存。现在的商业虚拟机都采用这种收集算法来回收新生代IBM公司的专门研究表明新生代中的对象98%是“朝生夕死”的所以并不需要按照1:1的比例来划分内存空间而是将内存分为一块较大的Eden空间和两块较小的Survivor空间每次使用Eden和其中一块Survivor。当回收时将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1也就是每次新生代中可用内存空间为整个新生代容量的90%80%10%只有10%的内存会被“浪费”。当然98%的对象可回收只是一般场景下的数据我们没有办法保证每次回收都只有不多于10%的对象存活当Survivor空间不够用时需要依赖其他内存这里指老年代进行分配担保Handle Promotion。 标记-整理算法
复制收集算法在对象存活率较高时就要进行较多的复制操作效率将会变低。更关键的是如果不想浪费50%的空间就需要有额外的空间进行分配担保以应对被使用的内存中所有对象都100%存活的极端情况所以在老年代一般不能直接选用这种算法。根据老年代的特点有人提出了另外一种标记-整理Mark-Compact算法标记过程仍然与标记-清除算法一样但后续步骤不是直接对可回收对象进行清理而是让所有存活的对象都向一端移动然后直接清理掉端边界以外的内存。 分代收集算法
当前商业虚拟机的垃圾收集都采用分代收集Generational Collection算法这种算法并没有什么新的思想只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中每次垃圾收集时都发现有大批对象死去只有少量存活那就选用复制算法只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保就必须使用标记—清理或者标记—整理算法来进行回收。
jvm有哪些垃圾回收器实际中如何选择 图中展示了7种作用于不同分代的收集器如果两个收集器之间存在连线则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。 新生代收集器全部的都是复制算法Serial、ParNew、Parallel Scavenge 老年代收集器CMS标记-清理、Serial Old标记-整理、Parallel Old标记整理 整堆收集器 G1一个Region中是标记-清除算法2个Region之间是复制算法 同时先解释几个名词 1并行Parallel多个垃圾收集线程并行工作此时用户线程处于等待状态 2并发Concurrent用户线程和垃圾收集线程同时执行 3吞吐量运行用户代码时间运行用户代码时间垃圾回收时间 1.Serial收集器是最基本的、发展历史最悠久的收集器。 特点单线程、简单高效与其他收集器的单线程相比对于限定单个CPU的环境来说Serial收集器由于没有线程交互的开销专心做垃圾收集自然可以获得最高的单线程手机效率。收集器进行垃圾回收时必须暂停其他所有的工作线程直到它结束Stop The World。 应用场景适用于Client模式下的虚拟机。 Serial / Serial Old收集器运行示意图 2.ParNew收集器其实就是Serial收集器的多线程版本。 除了使用多线程外其余行为均和Serial收集器一模一样参数控制、收集算法、Stop The World、对象分配规则、回收策略等。 特点多线程、ParNew收集器默认开启的收集线程数与CPU的数量相同在CPU非常多的环境中可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。 和Serial收集器一样存在Stop The World问题 应用场景ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器因为它是除了Serial收集器外唯一一个能与CMS收集器配合工作的。 ParNew/Serial Old组合收集器运行示意图如下 3.Parallel Scavenge 收集器与吞吐量关系密切故也称为吞吐量优先收集器。 特点属于新生代收集器也是采用复制算法的收集器又是并行的多线程收集器与ParNew收集器类似。 该收集器的目标是达到一个可控制的吞吐量。还有一个值得关注的点是GC自适应调节策略与ParNew收集器最重要的一个区别 GC自适应调节策略Parallel Scavenge收集器可设置-XX:UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小-Xmn、Eden与Survivor区的比例-XX:SurvivorRation、晋升老年代的对象年龄-XX:PretenureSizeThreshold等虚拟机会根据系统的运行状况收集性能监控信息动态设置这些参数以提供最优的停顿时间和最高的吞吐量这种调节方式称为GC的自适应调节策略。 Parallel Scavenge收集器使用两个参数控制吞吐量 XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间 XX:GCRatio 直接设置吞吐量的大小。
4.Serial Old是Serial收集器的老年代版本。 特点同样是单线程收集器采用标记-整理算法。 应用场景主要也是使用在Client模式下的虚拟机中。也可在Server模式下使用。 Server模式下主要的两大用途在后续中详细讲解··· 在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用。 作为CMS收集器的后备方案在并发收集Concurent Mode Failure时使用。
Serial / Serial Old收集器工作过程图Serial收集器图示相同 5.Parallel Old是Parallel Scavenge收集器的老年代版本。 特点多线程采用标记-整理算法。 应用场景注重高吞吐量以及CPU资源敏感的场合都可以优先考虑Parallel ScavengeParallel Old 收集器。 Parallel Scavenge/Parallel Old收集器工作过程图 6.CMS收集器是一种以获取最短回收停顿时间为目标的收集器。
特点基于标记-清除算法实现。并发收集、低停顿。
应用场景适用于注重服务的响应速度希望系统停顿时间最短给用户带来更好的体验等场景下。如web程序、b/s服务。
CMS收集器的运行过程分为下列4步
初始标记标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
并发标记进行GC Roots Tracing 的过程找出存活对象且用户线程可并发执行。
重新标记为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
并发清除对标记的对象进行清除回收。 CMS收集器的内存回收过程是与用户线程一起并发执行的。 CMS收集器的工作过程图 CMS收集器的缺点 对CPU资源非常敏感。 无法处理浮动垃圾可能出现Concurrent Model Failure失败而导致另一次Full GC的产生。 因为采用标记-清除算法所以会存在空间碎片的问题导致大对象无法分配空间不得不提前触发一次Full GC。
7.G1收集器一款面向服务端应用的垃圾收集器。
特点如下
并行与并发G1能充分利用多CPU、多核环境下的硬件优势使用多个CPU来缩短Stop-The-World停顿时间。部分收集器原本需要停顿Java线程来执行GC动作G1收集器仍然可以通过并发的方式让Java程序继续运行。
分代收集G1能够独自管理整个Java堆并且采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
空间整合G1运作期间不会产生空间碎片收集后能提供规整的可用内存。
可预测的停顿G1除了追求低停顿外还能建立可预测的停顿时间模型。能让使用者明确指定在一个长度为M毫秒的时间段内消耗在垃圾收集上的时间不得超过N毫秒。 G1收集器运行示意图
关于gc的选择 除非应用程序有非常严格的暂停时间要求否则请先运行应用程序并允许VM选择收集器如果没有特别要求。使用VM提供给的默认GC就好。 如有必要调整堆大小以提高性能。 如果性能仍然不能满足目标请使用以下准则作为选择收集器的起点 如果应用程序的数据集较小最大约100 MB则选择带有选项-XX UseSerialGC的串行收集器。 如果应用程序将在单个处理器上运行并且没有暂停时间要求则选择带有选项-XX UseSerialGC的串行收集器。 如果a峰值应用程序性能是第一要务并且b没有暂停时间要求或可接受一秒或更长时间的暂停则让VM选择收集器或使用-XX UseParallelGC选择并行收集器 。 如果响应时间比整体吞吐量更重要并且垃圾收集暂停时间必须保持在大约一秒钟以内则选择具有-XX UseG1GC。值得注意的是JDK9中CMS已经被Deprecated不可使用移除该选项 如果使用的是jdk8并且堆内存达到了16G那么推荐使用G1收集器来控制每次垃圾收集的时间。 如果响应时间是高优先级或使用的堆非常大请使用-XXUseZGC选择完全并发的收集器。值得注意的是JDK11开始可以启动ZGC但是此时ZGC具有实验性质在JDK15中[202009发布]才取消实验性质的标签可以直接显示启用但是JDK15默认GC仍然是G1 这些准则仅提供选择收集器的起点因为性能取决于堆的大小应用程序维护的实时数据量以及可用处理器的数量和速度。 如果推荐的收集器没有达到所需的性能则首先尝试调整堆和新生代大小以达到所需的目标。 如果性能仍然不足尝试使用其他收集器 总体原则减少STOP THE WORD时间使用并发收集器比如CMSParNewG1来减少暂停时间加快响应时间并使用并行收集器来增加多处理器硬件上的总体吞吐量。
JVM8为什么要增加元空间
原因 1、字符串存在永久代中容易出现性能问题和内存溢出。 2、类及方法的信息等比较难确定其大小因此对于永久代的大小指定比较困难太小容易出现永久代溢出太大则容易导致老年代溢出。 3、永久代会为 GC 带来不必要的复杂度并且回收效率偏低。
JVM8中元空间有哪些特点
1每个加载器有专门的存储空间。 2不会单独回收某个类。 3元空间里的对象的位置是固定的。 4如果发现某个加载器不再存货了会把相关的空间整个回收
如何解决线上gc频繁的问题 查看监控以了解出现问题的时间点以及当前FGC的频率可对比正常情况看频率是否正常 了解该时间点之前有没有程序上线、基础组件升级等情况。 了解JVM的参数设置包括堆空间各个区域的大小设置新生代和老年代分别采用了哪些垃圾收集器然后分析JVM参数设置是否合理。 再对步骤1中列出的可能原因做排除法其中元空间被打满、内存泄漏、代码显式调用gc方法比较容易排查。 针对大对象或者长生命周期对象导致的FGC可通过 jmap -histo 命令并结合dump堆内存文件作进一步分析需要先定位到可疑对象。 通过可疑对象定位到具体代码再次分析这时候要结合GC原理和JVM参数设置弄清楚可疑对象是否满足了进入到老年代的条件才能下结论。
内存溢出的原因有哪些如何排查线上问题 java.lang.OutOfMemoryError: ......java heap space..... 堆栈溢出代码问题的可能性极大 java.lang.OutOfMemoryError: GC over head limit exceeded 系统处于高频的GC状态而且回收的效果依然不佳的情况就会开始报这个错误这种情况一般是产生了很多不可以被释放的对象有可能是引用使用不当导致或申请大对象导致但是java heap space的内存溢出有可能提前不会报这个错误也就是可能内存就直接不够导致而不是高频GC. java.lang.OutOfMemoryError: PermGen space jdk1.7之前才会出现的问题 原因是系统的代码非常多或引用的第三方包非常多、或代码中使用了大量的常量、或通过intern注入常量、或者通过动态代码加载等方法导致常量池的膨胀 java.lang.OutOfMemoryError: Direct buffer memory 直接内存不足因为jvm垃圾回收不会回收掉直接内存这部分的内存所以可能原因是直接或间接使用了ByteBuffer中的allocateDirect方法的时候而没有做clear java.lang.StackOverflowError - Xss设置的太小了 java.lang.OutOfMemoryError: unable to create new native thread 堆外内存不足无法为线程分配内存区域 java.lang.OutOfMemoryError: request {} byte for {}out of swap 地址空间不够