声明:本节内容转载于 @pdai:JVM基础 - Java内存模型引入。
很多人都无法区分Java内存模型和JVM内存结构,以及Java内存模型与物理内存之间的关系。本文从堆栈角度引入JMM,然后介绍JMM和物理内存之间的关系。@pdai
声明:本章节转载自 Info 上 深入理解Java内存模型。PDF文档下载 深入理解Java内存模型【程晓明】
作者:程晓明。
说明:这篇文章对JMM讲得很清楚了,大致分三部分:重排序与顺序一致性;三个同步原语(lock,volatile,final)的内存语义,重排序规则及在处理器中的实现;Java内存模型的设计,及其与处理器内存模型和顺序一致性内存模型的关系。
声明:此章节内容整理自:@pdai:调试排错 - Java 问题排查之Linux命令文本操作
内存泄漏(memory leak):在Java中如果不再使用一个对象,但是该对象依然在GC ROOT的引用链上,这个对象就不会被垃圾回收器回收,这种情况就称之为内存泄漏。解决内存溢出的思路:
内存溢出:指的是内存的使用量超过了Java虚拟机可以分配的上限,最终产生了内存溢出OutOfMemory的错误。
- PS:内存泄漏绝大多数情况都是由堆内存泄漏引起的。
声明:此节内容主要整理自@pdai:调试排错 - Java 问题排查之工具单,在此基础上做了改动。jps:查看当前进程
jps是JDK提供的一个查看当前Java进程的小工具, 可以看做是Java Virtual Machine Process Status Tool的缩写jps常用命令
Java程序在启动以后,会在Java.io.tmpdir指定的目录下,就是临时文件夹里,生成一个类似于hsperfdata_User的文件夹,这个文件夹里(在Linux中为/tmp/hsperfdata_{userName}/),有几个文件,名字就是Java进程的pid,因此列出当前运行的Java进程,只是把这个目录里的文件名列一下而已。至于系统的参数什么,就可以解析这几个文件获得更多请参考 jps - Java Virtual Machine Process Status Tool
jstack是JDK自带的线程堆栈分析工具,使用该命令可以查看或导出 Java 应用程序中线程堆栈信息jstack常用命令:
jinfo 是 JDK 自带的命令,可以用来查看正在运行的 Java 应用程序的扩展参数,包括Java System属性和JVM命令行参数;也可以动态的修改正在运行的 JVM 一些参数。当系统崩溃时,jinfo可以从core文件里面知道崩溃的Java应用程序的配置信息jinfo常用命令:
JDK自带的jmap是一个多功能的命令。它可以生成 Java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。两个用途
- PS:dump文件是什么去这里:https://www.cnblogs.com/toSeeMyDream/p/7151635.html
缺点:无法精确到GC产生的时间,只能用于判断GC是否存在问题。jstat参数众多,但是使用一个就够了。
top除了看一些基本信息之外,剩下的就是配合来查询vm的各种问题了。top命令是Linux下用来查看系统信息的一个命令,它提供给我们去实时地去查看系统的资源,比如执行时的进程、线程和系统参数等信息。
缺点:只能查看最基础的进程信息,无法查看到每个部分的内存占用(堆、方法区、堆外)
关于下列两个概念的说明:
- 常驻内存:当前进程总的使用了多少内存。
- PS:常驻内存包含了“共享内存”,所以当前进程真正使用的内存是:常驻内存 - 共享内存。
- 共享内存:当前进程第三方依赖需要的内存。只加载一次,其他地方就可以用了,故而称为“共享”。
Jconsole (Java Monitoring and Management Console),JDK自带的基于JMX的可视化监视、管理工具 官方文档可以参考这里本地连接 或 远程连接:
路径:JDK\bin\jconsole.exe
注:远程连接在“测试环境”用就可以了,别在线上环境用。
VisualVM 是一款免费的,集成了多个 JDK 命令行工具的可视化工具,整合了命令行 JDK 工具和轻量级分析功能,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。本地连接:JDK\bin\jvisualvm.exe的方式,这种是中文版
注:这款软件在Oracle JDK 6~8 中发布(路径:JDK\bin\jvisualvm.exe),但是在 Oracle JDK 9 之后不在JDK安装目录下需要单独下载。下载地址:https://visualvm.github.io/
优点:支持Idea插件,开发过程中也可以使用。
缺点:对大量集群化部署的Java进程需要手动进行管理。
注:只可用于“测试环境”,不可用于“生产环境”。因为操作VisualVM中提供的功能时会停掉线程,从而影响用户。
官网地址:https://arthas.aliyun.com/doc/tunnel.html大概流程如下:
优点:
- 功能强大,不止于监控基础的信息,还能监控单个方法的执行耗时等细节内容。
- 支持应用的集群管理.
注:需要去官网下载 tunnel的jar包丢在服务器目录中,
排错:在arthas-tunnel-server-下载的某版本-fatjar.jar所在的目录中有一个nohup.out文件,打开即可排错,如:有些服务没注册上来之类的。Eclipse Memory Analyzer (MAT)
这玩意儿可以说在开发中都会接触到,所以需要好好了解一下。先来了解三个东西:也是MAT的原理
MAT 是一种快速且功能丰富的 Java 堆分析器,可帮助你发现内存泄漏并减少内存消耗。MAT在的堆内存分析问题使用极为广泛,需要重点掌握。
可以在这里下载, 官方文档可以看这里
提示:启动时可能会提示某某版本的JDK不支持,需要某某版本或以上,那安装对应的JDK版本,然后将其直到bin目录的路径放到path配置中即可,但:建议将此版本配置移到最上面或比其他版本的JDK更靠上。
浅堆(Shallow Heap):支配树中对象本身占用的空间。
深堆(Retained Heap):支配树中对象的子树就是所有被该对象支配的内容,这些内容组成了对象的深堆(Retained Heap),也称之为保留集( Retained Set ) 。深堆的大小表示该对象如果可以被回收,能释放多大的内存空间。
MAT内存泄漏检测的原理:MAT就是根据支配树,从叶子节点向根节点遍历,如果发现深堆的大小超过整个堆内存的一定比例阈值,就会将其标记成内存泄漏的“嫌疑对象”。使用MAT发现问题
使用内存快照的目的:找出是程序哪里引发的问题、定位到问题出现的地方。生成内存快照的Java虚拟机参数:
服务器中导出运行中系统的内存快照的简单方式:场景为内存在持续增长,但未发生内存泄漏,所以上面的-XX:+HeapDumpOnOutOfMemoryError就不能用。
导出的dump文件还可以直接使用在线工具 HeapHero 打开来分析。
生成的堆内存报告很大怎么办?机器内存范围之内的快照文件,直接使用MAT打开分析即可。
注意:服务器中放MAT的目录记得将读写权限打开。之后通过MAT中的脚本生成分析报告:生成的报告就是像上面那种静态页面
注意:默认MAT分析时只使用了1G的堆内存,如果快照文件超过1G,需要修改MAT目录下的MemoryAnalyzer.ini配置文件调整最大堆内存(-Xmx值)。
涉及到SpringMVC时,怎么定位到是哪个接口导致的问题?
内存快照分析:优点:有完整的内存快照,从而能更准确地判断出问题的原因。
在线定位优点:无需生成内存快照,整个过程对用户的影响“较小”。
Arthas的stacke在线定位大致思路
注意:别忘了把Arthas的jar包上传到服务器目录中,不然下面的命令能用个毛线。
btrace是一个在Java 平台上执行的追踪工具,可以有效地用于线上运行系统的方法追踪,具有侵入性小、对性能的影响微乎其微等特点。是生产环境&预发的排查问题大杀器。项目中可以使用btrace工具,实现定制化,打印出方法被调用的栈信息等等。使用方法:btrace 具体可以参考这里:https://github.com/btraceio/btrace
注意:需要配置环境变量BTRACE_HOME,和配置JDK是一样的。
上面示例看起来懵的话,直接去看这个示例:https://www.cnblogs.com/wei-zw/p/9502274.htmlIDEA本地调试和远程调试
声明:Debug用来追踪代码的运行流程,通常在程序运行过程中出现异常,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化;并且在实际的排错过程中,还会用到Remote Debug IDEA 。相比 Eclipse/STS效率更高,本文主要介绍基于IDEA的Debug和Remote Debug的技巧。
- 前面9个部分,主要总结自 https://www.cnblogs.com/diaobiyong/p/10682996.html
- 远程调试,主要整理自 https://www.jianshu.com/p/302dc10217c0
- 著作权归相关作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
首先说第一组按钮,共8个按钮,从左到右依次如下:
第二组按钮,共7个按钮,从上到下依次如下:
更新程序
在Debug过程中,跟踪查看变量的变化是非常必要的,这里就简单说下IDEA中可以查看变量的几个地方,相信大部分人都了解。如下,在IDEA中,参数所在行后面会显示当前变量的值
在前面提到的计算表达式如下图的按钮,Evaluate Expression (Alt + F8) 可以使用这个操作在调试过程中计算某个表达式的值,而不用再去打印信息
想想,一行代码里有好几个方法,怎么只选择某一个方法进入。之前提到过使用Step Into (Alt + F7) 或者 Force Step Into (Alt + Shift + F7)进入到方法内部,但这两个操作会根据方法调用顺序依次进入,这比较麻烦那么智能步入就很方便了,智能步入,这个功能在Run -> Debugging Action里可以看到,Smart Step Into (Shift + F7),如下图
通过设置断点条件,在满足条件时,才停在断点处,否则直接运行。
通常,当我们在遍历一个比较大的集合或数组时,在循环内设置了一个断点,难道我们要一个一个去看变量的值?那肯定很累,说不定你还错过这个值得重新来一次。
设置当前断点的条件在断点上右键直接设置当前断点的条件,如下图设置exist为true时断点才生效
查看所有断点点击View Breakpoints (Ctrl + Shift + F8),查看所有断点
右边的Filters过滤再说说右边的Filters过滤:这些一般情况下不常用,简单说下意思
异常断点:通过设置异常断点,在程序中出现需要拦截的异常时,会自动定位到异常行如下图,点击+号添加Java Exception Breakpoints,添加异常断点然后输入需要断点的异常类
一般情况下,我们调试的时候是在一个线程中的,一步一步往下走。但有时候你会发现在Debug的时候,想发起另外一个请求都无法进行了?那是因为IDEA在Debug时默认阻塞级别是ALL,会阻塞其它线程,只有在当前调试线程走完时才会走其它线程。可以在View Breakpoints里选择Thread,如下图,然后点击Make Default设置为默认选项。
在调试的时候,想要重新走一下流程而不用再次发起一个请求?首先认识下这个方法调用栈,如下图,首先请求进入DemoController的insertDemo方法,然后调用insert方法,其它的invoke我们且先不管,最上面的方法是当前断点所在的方法。
断点回退所谓的断点回退,其实就是回退到上一个方法调用的开始处,在IDEA里测试无法一行一行地回退或回到上一个断点处,而是回到上一个方法。
- 注意:断点回退只是重新走一下流程,之前的某些参数/数据的状态已经改变了的是无法回退到之前的状态的,如对象、集合、更新了数据库数据等等。
想要在Debug的时候,中断请求,不要再走剩余的流程了?有些时候,我们看到传入的参数有误后,不想走后面的流程了,怎么中断这次请求呢(后面的流程要删除数据库数据呢....),难道要关闭服务重新启动程序?嗯,我以前也是这么干的。
有时候,本地调试的时候没有问题,打包部署到测试环境的时候却爆出一堆莫名其妙的问题,这时该怎么办呢?使用特定JVM参数运行服务端代码
特别注意:用于远程debug的代码必须与远程部署的代码完全一致,不能发生任何的修改,否则打上的断点将无法命中,切记切记!远程debug模式已经开启,现在可以在需要调试的代码中打断点了,比如:
参考资料:Arthas简介
在学习Arthas之前,推荐先看后面的美团技术团队的 [Java 动态调试技术原理](#Java 动态调试技术原理),这样你会对它最底层技术有个了解。可以看下文中最后有个对比图:Greys(Arthas也是基于它做的二次开发)和Java-debug-toolArthas是什么
官方地址:https://arthas.aliyun.com/doc/dashboard.html
官方地址:https://arthas.aliyun.com/doc/web-console.html
注意:profiler这个命令无法在Windows中运行,可以在Linux或MacOS中运行。JVM相关
请注意:这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 shutdown 或 stop 亦或将增强过的类执行 reset 命令
场景:我想看下查看最繁忙的线程,以及是否有阻塞情况发生? 常规查看线程,一般我们可以通过 top 等系统命令进行查看,但是那毕竟要很多个步骤,很麻烦
场景:我新写了一个类或者一个方法,我想知道新写的代码是否被部署了?
场景:我新修改的内容在方法内部,而上一个步骤只能看到方法,这时候可以反编译看下源码
场景:我想看下我新加的方法在线运行的参数和返回值?或者是 在使用trace定位到性能较低的方法之后,使用watch命令监控该方法,获得更为详细的方法信息。
对该命令注意前面说的 monitor/watch/trace相关 注意事项,监控完了需要使用命令结束。具体看 watch 命令
场景:我想看下某个方法的调用栈的信息?
场景:testMethod这个方法入口响应很慢,如何找到最耗时的子调用?即方法嵌套,找出具体是哪个方法耗时。
对该命令注意前面说的 monitor/watch/trace相关 注意事项,监控完了需要使用命令结束。运行此命令之后需要即时触发方法才会有响应的信息打印在控制台上,然后一层一层看子调用。
场景:我找到了问题所在,能否线上直接修改测试,而不需要在本地改了代码后,重新打包部署,然后重启观察效果?
场景:我想看下某个方法的性能
场景:通过前面的排查之后,发现是偏底层的API耗时长,我怎么确定是底层什么原因导致的?如:for循环中向ArrayList添加数据。
可以采用trace+watch,但稍微麻烦,下面说明 profiler 命令生成性能监控火焰图来直观的查看。
火焰图中一般找绿色部分Java中栈顶上比较平(比较宽)的部分,很可能就是性能的瓶颈。
本文转载自 美团技术团队胡健的Java 动态调试技术原理及实践, 通过学习Java agent方式进行动态调试了解目前很多大厂开源的一些基于此的调试工具简介
注意:使用时去掉下面的注释
GC调优:指的是对垃圾回收(Garbage Collection)进行调优。GC调优的主要目标是避免由垃圾回收引起程序性能下降。GC调优的核心分成三部分:
GC调优无唯一的标准答案,如何调优与硬件、程序本身、使用情况均有关系。本章节主要说明的是需要的工具和基本方法。
企业中所要追求的便是:高吞吐量、低延迟、低内存使用量。GC调优的一般步骤:和前面玩的排错思路是一套的
缺点:无法精确到GC产生的时间,只能用于判断GC是否存在问题。
适合开发时使用,缺点:对程序运行性能有一定影响,且生产环境我们一般没有相应权限进行操作。
怎么找JDK对应版本所支持的所有JVM参数?
下列是JDK8的常见JVM参数说。
- 进入网址选择自己要的JDK版本:https://docs.oracle.com/en/java/javase/index.html
- 找到JavaSE Tools Reference:版本不同名字有区别,基本都是这个名字 或者 Tools -> [JDK] Tools Reference / Specifications。
- 找到 java选项 点进去即可。
注:最合理的设置方式应该是根据最大并发量估算服务器的配置,然后再根据服务器配置计算最大堆内存的值。-Xms参数:用来设置初始堆大小,建议将-Xms设置的和-Xmx一样大,有以下几点好处:
由于JVM底层设计极为复杂,一个参数的调整也许让某个接口得益,但同样有可能影响其他更多接口。因此接下来的参数不建议手动调整。
注意:该参数设置完是不会生效的,必须开启 -XX:+UseCmsInitiatingOccupancyOnly 参数。
同时:该参数是通过另外几个参数计算出的阈值
- ((100 - MinHeapFreeRatio)+(double)(CMSTriggerRatio * MinHeapFreeRatio)/ 100.0)
-XX:+PrintFlagsInitial 参数:显示在处理参数之前所有可设置的参数及它们的值,然后直接退出程序。
这里面的内容其实在前面知识体系中已经概括进去了,这里单独弄出来方便初学者理清思路。
性能调优的思路和内存排查、GC调优思路差不多。因为修复问题和测试验证需要具体开发场景演示,因此这里主要说明发现问题和诊断问题。
较常见的性能问题现象
线程转储(Thread Dump):对所有运行中的线程当前状态的快照。
线程转储可以通过jstack、visualvm等工具获取。其中包含了线程名、优先级、线程ID、线程状态、线程栈信息等等内容,可以用来解决CPU占用率高、死锁等问题。
这里说明的是最基础的一种,但麻烦的方式,只起引导思路的作用。
注意:需要将之前记录下的十进制线程ID号(第2步获取的)转换成16进制。另外什么更简单、方法嵌套排查、性能问题等等直接去前面的Arthas的内容:Arthas场景实战。
可以通过计算器,也可以通过 printf ‘%x\n’ 线程ID 命令直接获得16进制下的线程ID。
线程耗尽问题:程序在启动运行一段时间之后,就无法接受任何请求了。将程序重启之后继续运行,依然会出现相同的情况。线程耗尽问题,一般是由于执行时间过长:
线程死锁问题的定位方式
这样做是不准确的。OpenJDK中提供了一款叫 JMH(Java Microbenchmark Harness)的工具,可以准确地对Java代码进行基准测试,量化方法的执行性能。
第一:测试时有些对象创建是懒加载的,所以会影响第一次的请求时间;
第二:因为虚拟机中JIT(即时编译器)会优化你的代码,所以上面的测试方式得出的时间并不一定是最终用户处理的时间。
简单搭建JMH环境
测试结果可以通过 https://jmh.morethan.io/ 转为可视化。格式的是JSON格式(new OptionsBuilder()..resultFormat(ResultFormatType.JSON)可以构建为JSON格式)方式一(推荐):通过maven的verify命令,检测代码问题并打包成jar包。然后通过 java -jar 打成的jar包 命令执行基准测试。
若是需要再提升,追求更高的性能、更小的CPU和内存开销,则可以使用Oracle提供的另一款高性能JDK:GraalVM 。这玩意儿有两种模式:
GraalVM的使用场景:传统的系统架构有人力、机房开销大;流量激增需要大量服务器,流量过后很多服务器就闲置造成资源浪费,而因为虚拟化技术、云原生技术,所以GraalVM+云服务商提高的Serverless无服务器化架构就是其使用场景了。
- JIT(Just-In-Time)模式:可跨平台,就是“一次编写,到处运行”。预热之后,通过内置的Graal即时编译器优化热点代码,生成比Hotspot JIT更高性能的机器码。这种模式提高性能,社区版对CPU占有率和内存提高不明显(企业版有显著提高)。
- AOP(Ahead-Of-Time)模式:不可跨平台,而是为特定平台创建可执行文件,本质是本地镜像(Native Image)。这种模式对启动速度、CPU、内存开销都有了显著提高。但是此模式也有另外问题:
- 跨平台问题:在不同平台下运行需要编译多次。编译平台的依赖库等环境要与运行平台保持一致。
- 消耗大量的CPU和内存:使用框架之后,编译本地镜像的时间比较长,同时也需要消耗大量的CPU和内存。
- AOT 编译器在编译时,需要知道运行时所有可访问的所有类。但是Java中有一些技术可以在运行时创建类,例如反射、动态代理等。这些技术在很多框架比如Spring中大量使用,所以框架需要对AOT编译器进行适配解决类似的问题。
br />对应MD文档
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) | Powered by Discuz! X3.4 |