00-谈GUI工具前的补充
补充1:内存泄漏
内存泄漏的理解与分类
何为内存泄漏( memory leak)
可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。
内存泄漏( memory leak)的理解
严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。
但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄漏”。
对象X引用对象Y,X的生命周期比Y的生命周期长;
那么当Y生命周期结束的时候,X依然引用着Y,这时候,垃圾回收期是不会回收对象Y的;
如果对象x还引用着生命周期比较短的A、B、C,对象A又引用着对象 a、b、c,这样就可能造成大量无用的对象不能被回收,进而占据了内存资源,造成内存泄漏,直到内存溢出。
内存泄漏与内存溢出的关系:
1.内存泄漏(memory leak )
申请了内存用完了不释放,比如一共有1024M 的内存,分配了512M 的内存一直不回收,那么可以用的内存只有512M 了,仿佛泄露掉了一部分;
通俗一点讲的话,内存泄漏就是【占着茅坑不拉shi】。
2.内存溢出(out of memory)
申请内存时,没有足够的内存可以使用;
通俗一点儿讲,一个厕所就三个坑,有两个站着茅坑不走的(内存泄漏),剩下最后一个坑,厕所表示接待压力很大,这时候一下子来了两个人,坑位(内存)就不够了,内存泄漏变成内存溢出了。
可见,内存泄漏和内存溢出的关系: 内存泄漏的增多,最终会导致内存溢出。
泄漏的分类
经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存;
偶然发生:在某些特定情况下才会发生;
一次性:发生内存泄露的方法只会执行一次;
隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。
Java中内存泄漏的8种情况
1-静态集合类
静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
2-单例模式
单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和JVM 的生命周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。
3-内部类持有外部类
内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象。
这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏。
4-各种连接,如数据库连接、网络连接和IO连接等
各种连接,如数据库连接、网络连接和IO连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。
否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
5-变量不合理的作用域
变量不合理的作用域。一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。
如下面的伪代码
如上面这个伪代码,通过readFronNet方法把接受的消息保存在变量msg中,然后调用saveDB方法把msg的内容保存到数据库中,此时msg已经就没用了,由于msg的生命周期与对象的生命周期相同,此时msg还不能回收,因此造成了内存泄漏。
实际上这个msg变量可以放在receiveMsg方法内部,当方法使用完,那么msg的生命周期也就结束,此时就可以回收了。还有一种方法,在使用完msg后,把msg设置为null,这样垃圾回收器也会回收msg的内存空间。
6-改变哈希值
改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。
否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏。
这也是 string为什么被设置成了不可变类型,我们可以放心地把 String存入 HashSet,或者把String当做HashMap 的key值;
当我们想把自己定义的类保存到散列表的时候,需要保证对象的 hashCode 不可变。
7-缓存泄漏
内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据。
对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。
8-监听器和回调
内存泄漏第三个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚。
需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为WeakHashMap中的键。
补充2:支持使用OQL语言查询对象信息
MAT支持一种类似于SQL的查询语言OQL(Object Query Language)。OQL使用类SQL语法,可以在堆中进行对象的查找和筛选。
SELECT子句
在NAT中,Select子句的格式与SQL基本一致,用于指定要显示的列。Select子句中可以使用“*”,查看结果对象的引用实例(相当于outgoing references)。
SELECT * FROM java.util.Vector v
使用“OBECTS”关键字,可以将返回结果集中的项以对象的形式显示。
SELECT objects v.elementData FROM java.util.Vector v
SELECT OBECTS s.value FROM java.lang.String s
在Select子句中,使用“AS RETAINED SET”关键字可以得到所得对象的保留集。
SELECT AS RETAINED SET *FROM com.fs.mat. Student
“DISTINCT”关键字用于在结果集中去除重复对象。
SELECT DISTINCT OBJECTS classof(s)FROM java.lang.String s
FROM子句
From子句用于指定查询范围,它可以指定类名、正则表达式或者对象地址。
SELECT *FROM java. lang.String s
下例使用正则表达式,限定搜索范围,输出所有com.fs包下所有类的实例
SELECT *FROM “com\ .fs…*”
也可以直接使用类的地址进行搜索。使用类的地址的好处是可以区分被不同ClassLoader加载的同一种类型。
select* from 0x37a0b4d
WHERE子句
Where子句用于指定0QL的查询条件。0QL查询将只返回满足where子句指定条件的对象。Where子句的格式与传统SQL极为相似。
下例返回长度大于10的char数组。
SELECT* FROM char[] s WHERE s.@length>10
下例返回包含“java”子字符串的所有字符串,使用“LIKE”操作符,“LIKE”操作符的操作参数为正则表达式。
SELECT * FROM java.lang.String s WHERE toString(s)LIKE “.*java.*”
下例返回所有value域不为null的字符串,使用“=”操作符。
SELECT * FROM java.lang.String s where s.value!=null
Where子句支持多个条件的AND、OR运算。下例返回数组长度大于15,并且深堆大于1000字节的所有Vector对象。
SELECT * FROM java.util.Vector v WHERE v.elementData.@length>15 AND v.@retainedHeapsize>1000
内置对象与方法
0QL中可以访问堆内对象的属性,也可以访问堆内代理对象的属性。访问堆内对象的属性时,格式如下:
[ <alias>. ] <field> . <field>. <field>其中alias为对象名称。
访问java.io.File对象的path属性,并进一步访问path的value属性:
SELECT toString(f.path.value) FROM java.io.File f
下例显示了String对象的内容、objectid和objectAddress。
SELECT s.toString(),s.@objectId,s.@objectAddress FROMjava.lang.String s
下例显示java.util.Vector内部数组的长度。
SELECT v.elementData.@length FROM java.util.Vector v
下例显示了所有的java.util.Vector对象及其子类型
select *from INSTANCEOF java.util.Vector
01-工具概述
使用命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但它们存在下列局限:
1.无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)。
2.要求用户登录到目标Java应用所在的宿主机上,使用起来不是很方便。3.分析数据通过终端输出,结果展示不够直观。
为此,JDK提供了一些内存泄漏的分析工具,如jconsole,jvisualvm等,用于辅助开发人员定位问题,但是这些工具很多时候并不足以满足快速定位的需求。所以这里我们介绍的工具相对多一些、丰富一些。
图形化综合诊断工具
JDK自带的工具
jconsole
jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信
息、永久区(或元空间)使用情况、类加载情况等
—>位置:jdk \bin\jconsole.exe
Visual VM
Visual VM是一个工具,它提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。
—>位置:jdk\binljvisualvm.exe
JMC:Java Mission Control
JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据。
第三方工具
MAT:MAT(Memory Analyzer Tool)
MAT:MAT(Memory Analyzer Tool)是基于Eclipse的内存分析工具,是一个快
速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
—>Eclipse的插件形式
JProfiler
JProfiler:商业软件,需要付费。功能强大。
—>与VisualVM类似 I
Arthas
Arthas:Alibaba开源的Java诊断工具。深受开发者喜爱。
Btrace
Btrace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构
造函数调用和系统内存等信息。
02-jConsole 了解
基本概述
从Java5开始,在JDK中自带的java监控和管理控制台。
用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。
官方教程:
https://docs.oracle.com/javase/7/docs/technotes/guides/management/jconsole.htm1
启动
cmd命令启动(需要配置环境变量)
三种连接方式
Local
使用JConsole连接一个正在本地系统运行的JVM,并且执行程序的和运行JConsole的需要
是同一个用户。JConsole使用文件系统的授权通过RMI连接器连接到平台的MBean服务器上。
这种从本地连接的监控能力只有Sun的JDK具有。
Remote
使用下面的URL通过RMI连接器连接到一个JMX代理,servicejm.rmi://jindi/rmi:/hostName:portNum/jimkrmi。
JConsole为建立连接,需要在环境变量中设置mx.remote.credentials来指定用户名和密码,从而进行授权。
Advanced
使用一个特殊的URL连接JMX代理。一般情况使用自己定制的连接器而不是RMI提供的连接器
来连接JMX代理,或者是一个使用JDK1.4的实现了JMX和JMX Rmote的应用。
03-Visual VM 重要
基本概述
Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具。
它集成了多个JDK命令行工具,使用visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。
在JDK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisualVM在JDK/bin目录下),即:它完全免费。
此外,Visual VM也可以作为独立的软件安装:
首页: https: //visualvm.github.io/index.html
插件的安装
Visual VM的一大特点是支持插件扩展,并且插件安装非常方便。我们既可以通过离线下载插件文件*.nbm,然后在Plugin对话框的已下载页面下,添加已下载的插件。也可以在可用插件页面下,在线安装插件。(这里建议安装上:VisualGC)
插件地址: https://visualvm.github.io/pluginscenters.html
安装VisualGC插件
连接方式
本地连接
监控本地Java进程的CPU、类、线程等
远程连接
1-确定远程服务器的ip地址
2-添加JMX(通过JMX技术具体监控远端服务器哪个Java进程
3-修改bin/catalina.sh文件,连接远程的tomcat
4-在…/conf中添加jmxremote.access和jmxremote.password文件
5-将服务器地址改为公网ip地址
6-设置阿里云安全策略和防火墙策略
7-启动tomcat,查看tomcat启动日志和端口监听
8-JMX中输入端口号、用户名、密码登录
主要功能
1-生成/读取堆内存快照
查看其他dump文件
2-查看JVM参数和系统属性
3-查看运行中的虚拟机进程
4-生成/读取线程快照
5.程序资源的实时监控
6.其他功能
远程环境监控
CPU分析和内存分析
JMX代理连接
04-eclipse MAT
MAT(Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况。
MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便。大家可以在
https://www.eclipse.org/mat/downloads.php下载并使用MAT。
运行独立版本的内存分析器所需的最低 Java 版本是 Java 11。请参阅JRE/JDK 来源。
本次不做此工具的详解
05-JProfiler 重要
基本概述
介绍
在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。在
eclipse里面有 Eclipse Memory Analyzer tool(MAT)插件可以测试,而在IDEA中也有这么一个插件,就是JProfiler。
JProfiler是由 ej-technologies 公司开发的一款 Java应用性能诊断工具。功能强大,但是收费。
官网下载地址: https : //www.ej-technologies.com/products/jprofiler/overview.html
特点
特点:
·使用方便、界面操作友好(简单且强大)
·对被分析的应用影响小(提供模板)
. CPU, Thread ,Memory分析功能尤其强大
·支持对jdbc , noSql,jsp, servlet, socket等进行分析
·支持多种模式(离线,在线)的分析
·支持监控本地、远程的VM
·跨平台,拥有多种操作系统的安装版本
主要功能
1-方法调用
对方法调用的分析可以帮助您了解应用程序正在做什么,并找到提高其性能的方法
2-内存分配
通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄漏问题,优化内存使用
3-线程和锁
JProfiler提供多种针对线程和锁的分析视图助您发现多线程问题
4-高级子系统
许多性能问题都发生在更高的语义级别上。例如,对于JDBC调用,您可能希望找出
执行最慢的SQL语句。JProfiler支持对这些子系统进行集成分析
安装与配置
下载与安装
官网下载地址: https : //www.ej-technologies.com/products/jprofiler/overview.html
官方下载安装就是
JProfiler中配置IDEA
IDEA集成JProfiler
点击idea上面的JProfiler图标即可启动当前程序
具体使用
数据采集方式
JProfier数据采集方式分为两种:Sampling(样本采集)和Instrumentation(重构模式)
注:JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为Profiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是Profiler的数据采集类型。
lnstrumentation重构模式
.Instrumentation:这是JProfiler全功能模式。在class加载之前,JProfier把相关功能代码写入到需要分析的class的bytecode中,对正在运行的jvm有一定影响。
优点:功能强大。在此设置中,调用堆栈信息是准确的。
缺点:若要分析的class较多,则对应用的性能影响较大,CPU开销可能很高(取决于Filter的控制)。因此使用此模式一般配合Filter使用,只对特定的类或包进行分析
Sampling抽样模式(一般推荐使用此方式)
.Sampling:类似于样本统计,每隔一定时间(5ms)将每个线程栈中方法栈中的信息统计出来
优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)
缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)
遥感监测Telemetries
内存视图Live Memory
Live memory 内存剖析: class/class instance的相关信息。例如对象的个数,大小,对象创建的方法执行栈,对象创建的热点。
所有对象A11 0bjects
显示所有加载的类的列表和在堆上分配的实例数。只有Java 1.5(JVMTI)才会显示此视图。
记录对象 Record objects
查看特定时间段对象的分配,并记录分配的调用堆栈。
分配访问树Allocation Call Tree
显示一棵请求树或者方法、类、包或对已选择类有带注释的分配信息的J2EE组件。
分配热点 Allocation Hot Spots
显示一个列表,包括方法、类、包或分配已选类的72EE组件。你可以标注当前值并且显示差异值。对于每个热点都可以显示它的跟踪记录树。
类追踪器class Tracker
类跟踪视图可以包含任意数量的图表,显示选定的类和包的实例与时间。
堆遍历heap walker
cpu视图cpu views
线程视图threads
Profiler通过对线程历史的监控判断其运行状态,并监控是否有线程阻塞产生,还能将一个线程所管理的方法以树状形式呈现。对线程剖析。
线程分析主要关心三个方面:
- web容器的线程最大数。比如: Tomcat的线程容量应该略大于最大并发数。
- 线程阻塞
- 线程死锁
线程历史Thread History
显示一个与线程活动和线程状态在一起的活动时间表。
线程监控Thread Monitor
显示一个列表,包括所有的活动线程以及它们目前的活动状况。
线程转储 Thread Dumps
显示所有线程的堆栈跟踪。
监视器&锁Monitors&locks
监控和锁Monitors & Locks所有线程持有锁的情况以及锁的信息。观察VM的内部线程并查看状态:
死锁探测图表Current Locking Graph :显示JVM中的当前死锁图表。
目前使用的监测器 Current Monitors :显示目前使用的监测器并且包括它们的关联线程。
锁定历史图表Locking History Graph :显示记录在VM中的锁定历史。
历史检测记录 Monitor History :显示重大的等待事件和阻塞事件的历史记录。
监控器使用统计 Monitor Usage Statistics :显示分组监测,线程和监测类的统计监测数据
06-Arthas 阿里巴巴 建议查阅官方文档
基本概述
概述
Arthas(阿尔萨斯)是Alibaba开源的Java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。
官方使用文档
https://arthas.aliyun.com/doc/index.html
安装与使用
下载arthas
直接使用此链接https://arthas.aliyun.com/arthas-boot.jar下载
Arthas Spring Boot Starter
https://arthas.aliyun.com/doc/spring-boot-starter.html
配置maven依赖:
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-spring-boot-starter</artifactId>
<version>${arthas.version}</version>
</dependency>
09-Flame Graphs(火焰图)
在追求极致性能的场景下,了解你的程序运行过程中cpu在干什么很重要,火焰图就是一种非常直观的展示cpu在程序整个生命周期过程中时间分配的工具。
火焰图对于现代的程序员不应该陌生,这个工具可以非常直观的显示出调用栈中的CPU消耗瓶颈。
网上的关于java火焰图的讲解大部分来自于Brendan Gregg的博客:
http://www.brendangregg.com/flamegraphs.html