Java常见问题排查

原因一般和Java的ClassLoader机制有关,

常见的问题是Jar包版本冲突问题,有的容易解决,编译时mvn做版本检测,如果版本不一样但是id一样,这样的时候编译会报错。

通常遇到的问题是,很多开源框架依赖了某个jar包,把这个jar包的代码拷过来打成自己的jar包,这时mvn不知道。

有可能用了新版本的方法,在生产环节因为加载了老的方法,就会找不到。

甚至可能出现的奇怪的现象是,生产环节100台机器,里面可能只有5台找不到,环境一模一样。原因是java在装载一个目录下所有jar包时,它加载的顺序完全取决于操作系统!!而Linux的顺序完全取决于INode的顺序,INode的顺序不完全能一致。

(INode讲解: Linux的inode的理解 - iTech - 博客园)

95台刚好新版本在前面,另外5台刚好是低版本在前面

淘宝遇到过发布时第一批是正常的,后几批全挂了。。。

理论上最正确的做法是java加载目录时按自己的顺序加载,这样不会出现集群不一致现象,要挂全挂~
 

这种问题排查起来不难,第一个方法加启动参数,它会把这个类是从哪个jar包加载的打印出来。

有问题的话一般是来源的jar包不符合期望。

第二个方法比较高端,不用加启动参数。

tomcat通常从自己的lib下加载jar包,还有应用WEB-INF/lib下加载jar包。可以去这两个目录下解压(-tvf不会真的解压,只是看列表),打印出所有的类,看是不是有同样包名同样类名的从两个不同的jar包出来,并且他们的md5出来是不一样的,如果如此,就是版本冲突了。

版本冲突意味着这个环境一直是存在风险的,就看命运了。。。

解决方法在mvn中做exclusion,难解决的是别的开源框架把它依赖的打包进去了

尽量在打包阶段中将版本冲突解掉。
 


OOM主要有七种,一般不会超过这七种异常
 


 

最常见的OOM的原因是这两个,GC超过限制和Heap被用满

这张图适用JDK7但是不适用JDK8,因为JDK8中没有PermGen

(参考:JDK8内存模型浅析—消失的PermGen JDK8内存模型浅析—消失的PermGen - 综合编程类其他综合 - 红黑联盟

Java 8: 从永久代(PermGen)到元空间(Metaspace) Java 8: 从永久代(PermGen)到元空间(Metaspace)_metaspace 是永久代吗_帅性而为1号的博客-CSDN博客)

通常-Xms和-Xmx在生产环节会设置成完全一样,

原因是如果不设置成一样,-Xms小(比如有人出于好心在不用内存时少用点),造成的结果是java内存到达一定大小后,觉得内存不够用了而又没到最大值,会做一遍GC把内存放大。当觉得要缩小,也会先做GC再缩小。

最后因为这两个参数设置不一样,造成了多次无用GC。所以线上生产环节不允许这两个值不同!
 

当Java堆满了就要知道堆都被谁用了,首先要有内存dump文件。

为了确保OOM后可以拿到dump文件,启动参数上都会加HeapDumpOnOutOfMemoryError。

作用是当java报OOM后会在target目录或工作目录下生成dump文件,结尾是.hprof

但是这个参数的问题是:只会在应用第一次OOM时dump,但是后面再OOM就不用生成了,因为默认已经有了

就有可能第一次OOM时的dump没用,后面的OOM才有用。

当登录的时候幸运地正在OOM,可以手工dump,用jmap -dump这个。但是这个不要随意在线上做!!因为当执行这条命令时,java会强制执行一次fullGC!

有时jmap会dump不出来,就不用强行dump了,这时可以尝试用gcore,先生成core dump,再用jmap从这个core dump中提取出。最好的做法是用gcore而不是jmap,因为gcore特别快,jmap特别慢。

因为jmap执行时除了fullGC,还会扫描整个java的heap,通常java堆有多大,生成的文件就有多大。

这是java排查的致命弱点,heap特别几十G时无法排查,重启就好了。。。业界没有很好地解决方案

拿到dump文件后,需要分析

在阿里可以用Zprofile这个网页版工具,可以在生产环境直接把dump文件拷过去,在Zprofile的机器上分析,这台机子内存有100多G。。。

通常文件分析出来以后需要看对象树的视图,不要用jmap -histo。

对象树视图的好处是可以看到哪个线程占的内存最多。

为了更精确找出问题,需要另一个神器,btrace。

它的作用是可以在java还在运行时,在另外一处挂一个脚本,来trace目前java进程所有动作。

唯一不能做的是不能改参数。建议所有java工程师尝试

但是在生产环境不要随意btrace,有人拿这个跟map的put,导致业务挂死P1故障。

性能优化也可以用btrace,因为可以跟踪进一个方法耗时多久,因为不可能每个方法都打印耗时多久。

前面都是铺垫,btrace才是终结技。

大多问题用以上方法可以解决~

特殊现象也有,系统在报OOM,但是dump后分析出来heap没用多少。出现过的原因是突然分配了个巨大的数组。
 

这种情况下,阿里版的jdk会打一行日志,关键字是allocating large。如果出现这个,说明分配了巨大数组。

还有一种现象是死循环,

之前出现过应用OOM了,死循环在循环体里不断创建对象,在dump的时候查不到是因为

报OOM的时候,这个线程已经退出了,引用关系已经没有了,对象不存活了。每次dump都在OOM后面导致排查不到。

那次的解决办法是写定时脚本,每三秒jmap -histo一次,从一天日志查。

新手遇到的问题是OOM打印的触发OOM的堆栈,但是很多时候这个堆栈里有关的人并不是导致OOM的人,因为OOM不是一个地方造成的,而是一个累积效应
 

多数问题是因为自增长的数据结构。

通常用的数据结构比如Collection都是自增长的,比如List<String> list = new ArrayList<String>();

第一次造成的严重故障是淘宝全挂,整个集群OOM,原因是有个hashmap太大了,里面全是用户信息。

阿里很多OOM都是因为这个。

应该线下用代码造OOM,尝试分析解决。
 

linux会给每个用户做限制,比如每个用户最多创建2000个线程,如果超过了2000就会报这个错。

登上出现这个问题的linux机时,输入-ls等指令都会报没有资源了,原因在于-ls也是创建线程。

机器上ulimit -a可以看到,但看到的不一定是真实的,因为Linux不光可以在用户维度限制,还可以在进程维度限制。

最好的方法是linux pro -pid 里面有linux文件,这个文件里的信息都是对的

内核层面还有个参数kernel.pid_max,pid是个数字,是有最大限制的。超过这个数就悲剧了

首先统计java进程创建了多少线程,ps -eLf | grep java -c可以统计。如果创建太多都是有bug

所有线程一定要起名字!!不然无法排查!!
 

这个问题和自定义数据结构很像,用线程池时很多人用Executors.newCachedThreadPool,但不明白newCachedThreadPool的意思是线程池最大可以创建int整型最大值个线程。

所以用线程池也必须设置大小!!

搞阿里开发规约建议 在阿里强制不允许用Executors.new.....,应该用new threadpoolExecutor(),清晰知道自己传的每个参数是什么意思。如果都不管的话都不知道自己创建的线程池会发生什么,所以必须用最原始的API。

开源界很多API很烂,不能随便用。
 

classLoader每装载一个类,都会把这个类的所有信息写进PermGen,包括类的名字方法的名字方法体等等。

类装载越多PermGen占用越多。Spring是导致PermGen变大的原因,因为Spring主要是把一个类生成一个新的类来增强。

Groovy脚本易犯错误是它默认情况下每装载一次,会生成一个新的类名,需要自己做一个cache,如果是一样的就用缓存的。

PermGen用满后,在java8之前会触发fullGC,很慢 全暂停。

java8以后叫 meta space size,另外一个参数控制大小

PermGen原因通常是装载类太多了,所以很容易查。用btrace跟踪谁调了ClassLoader.defineClass方法。

btrace可以跟踪java所有问题,因为所有API最后都会落到jdk上某个方法,跟这个方法的话可以跟到所有问题

比如PermGen可以用跟defineClass方法来追踪,看是谁一直创建Class

通常写通讯类程序的人看这个问题多一点,不管是Netty还是什么,背后在做网卡字节流装载是用Direct ByteBuffer

默认大小是Java堆多大,它设多大。在巧合情况下Java堆用了8G,DirectByteBuffer也用了8G,导致虚拟机挂掉

所有java启动参数的默认值,网上没有一份是正确的。需要用java自动工具去看,jinfo -flags


生产环节建议加这个参数,1G很稳。网络通讯很容易出这种问题

这个问题不是在java日志里看到,只会在另外一个单独文件看到。

出了这个问题,java进程一定已经挂掉了。

地址空间不够用在目64位环境下不会出现了
 

Deflater和Inflater要成对出现init end方法,如果没有成对出现肯定会泄露,这个方法是用来压缩数据包

(参考:Deflater和Inflater的用法 Deflater 和 Inflater 的用法_怎们判断数据流 deflater_Daniel_Cao_的博客-CSDN博客)
 

最好的方法是用代码把上面的场景都造出来,然后练习处理。

对Java内存掌握考验之一就是写段代码,用这段代码控制youngGC,fullGC并能够控制频率。
 

性能优化场景碰到。

GC太频繁的判断方法是:用top,看每个核的消耗,如果总是有一个核耗100%,通常是因为这个。看日志也可以知道

第三点是最复杂的,这说明代码写得好,充分利用了CPU和并行系统。第二点是代码写的烂。
 


jstat可以常用一点,可以看GC频率,java堆每个区域大小。建议少用jmap
 

阿里的应用优化到最后问题会集中在两个地方:

序列化反序列化 和 GC。

序列化和反序列化目前没有革命性的突破,只能想想能不能砍掉一些要序列化的项。
 

死锁好查的情况是jstat -l后看到JVM告诉说线程堆栈发生了死锁,看到最后告诉哪两个线程发生死锁。

死锁容易发生的原因是软件多人并行开发。

如果没有查到但是又怀疑死锁,除了jstack,还可以用pstack。

可以看C堆栈状况,因为java很多锁不在java堆栈而在C堆栈,因为jdk最后会调到C代码。

比如装载一个类,其实JVM会给这个类加一把锁,保证这个类只会初始化一次,这个锁可以在pstack看到但是不会在jstack看到
 

spring之前有死锁bug就是因为它的代码是多人写的。

处理线程池耗光 以前碰到过调用远程业务时没加超时时间。HSF默认有超时时间所以不会出现,但是自己写的HttpClient很容易忘记!外一调用的那端卡住了,自己这边也会全部卡住。所以所有分布式系统中只要访问另外一方,一定要设超时

hs_err_pid.log会描述为何退出,但是一般看不懂的。。

core dump一般都是已打开的。

生产环境可以看dmesg,看是不是操作系统杀掉了进程。因为虚拟机操作系统发现内存被用满后,通常会挑选一个内存消耗最大的进程杀掉。如果被杀掉,在dmesg中会有日志。如果是这样,是因为程序内存应用用的太多了
 


如果想看上面所说的日志,可以执行这行命令试试,作用是故意展示java怎么crash
 

如果是java上的线程栈溢出了会报stackoverflow,如果是native上的栈溢出了会直接导致java crash。

这种情况很容易判断,在这种场景下会生成core dump文件,但是不会生成hs_err_pid那个文件。core dump文件作用非常大!

jvm在jdk6以前bug很多,到7,8以后质量飙升。

排查所有问题时,最重要的是知道发生问题背后最根本的原因,知道java层面到底发生了什么。

做到这样可以不用动上层业务代码。

还要熟练运用工具。多尝试多用,不用等线上真实问题,学自己造问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值