java程序经常遇到的bug(7段小代码玩转Java程序常见的崩溃场景)
java程序经常遇到的bug(7段小代码玩转Java程序常见的崩溃场景)在现实情况中,内存泄漏通常都非常的隐蔽,需要借助Mat等工具才能找到根本原因。jmap、pmap等是常用的工具。我们可以使用上面同样的代码达到这个目的。下面的代码是死循环,持续向HashMap里塞数据,由于myMap属于GCRoots,始终得不到释放,所以它最终的结果就是OOM。importjava.util.HashMap; publicclassOOMDemo{ staticHashMap<Object Object>myMap=newHashMap<>(); publicstaticvoidstart()throwsException{ while(true){ myMap.put("key" counter "LargeStringgggggggggggggggggggggggggggg" "gggggggggggg
原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,非公众号转载保留此声明。
Java程序是基于GC的,在启动初始,就申请了足量的内存池,再加上JIT等编译器的实时优化,速度并不比直接用C 语言写的慢。Java语言同时由于反射和可观测等特点,再加上JFR这种神器,在发生问题的时候比二进制文件更容易找到它的根源。
最近在看RCA(Root Cause Analysis)的东西,不小心发现了yCrash这么个东西。它的几段问题小代码写的非常典型,我们可以稍微看一下,来看看Java应用程序常见的几个崩溃场景。
1.堆空间溢出OOM 一般是内存泄漏引起的,表现在 GC 日志里,一般情况下就是 GC 的时间变长了,而且每次回收的效果都非常一般。GC 后,堆内存的实际占用呈上升趋势。
下面的代码是死循环,持续向HashMap里塞数据,由于myMap属于GCRoots,始终得不到释放,所以它最终的结果就是OOM。
importjava.util.HashMap;
publicclassOOMDemo{
staticHashMap<Object Object>myMap=newHashMap<>();
publicstaticvoidstart()throwsException{
while(true){
myMap.put("key" counter "LargeStringgggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
counter);
counter;
}
}
}
2.内存泄漏
内存泄漏和内存溢出是一个道理,不同的是它的语意。
内存溢出可能是由于请求量过高,或者真实的业务需求需要所造成的后果,而内存溢出属于未知的、超出期望的OOM情况。
我们可以使用上面同样的代码达到这个目的。
在现实情况中,内存泄漏通常都非常的隐蔽,需要借助Mat等工具才能找到根本原因。jmap、pmap等是常用的工具。
比如,如果你忘记了重写对象的hashCode和equals方法,就会产生内存泄漏。
//leakexample:createdbyxjjdog2022
importjava.util.HashMap;
importjava.util.Map;
publicclassHashMapLeakDemo{
publicstaticclassKey{
Stringtitle;
publicKey(Stringtitle){
this.title=title;
}
}
publicstaticvoidmain(String[]args){
Map<Key integer>map=newHashMap<>();
map.put(newKey("1") 1);
map.put(newKey("2") 2);
map.put(newKey("3") 2);
Integerinteger=map.get(newKey("2"));
System.out.println(integer);
}
}
3.CPU飙升
直接一个死循环,就可以把CPU干死。
publicclassCPUSpikeDemo{
publicstaticvoidstart(){
newCPUSpikerThread().start();
newCPUSpikerThread().start();
newCPUSpikerThread().start();
newCPUSpikerThread().start();
newCPUSpikerThread().start();
newCPUSpikerThread().start();
System.out.println("6threadslaunched!");
}
}
publicclassCPUSpikerThreadextendsThread{
@Override
publicvoidrun(){
while(true){
//Justloopinginfinitely
}
}
}
获取问题代码通常可以使用下面的方法。
(1)使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid。使用 Shift P 快捷键可以按 CPU 的使用率进行排序。(2)再次使用 top 命令,加 -H 参数,查看某个进程中使用 CPU 最多的某个线程,记录线程的 ID。(3)使用 printf 函数,将十进制的 tid 转化成十六进制。(4)使用 jstack 命令,查看 Java 进程的线程栈。(5)使用 less 命令查看生成的文件,并查找刚才转化的十六进制 tid,找到发生问题的线程上下文。
4.线程泄漏线程资源是昂贵的。如果你不停的创建线程,系统资源很快就会被耗尽。下面的代码一直不停的创建线程,如果同时请求压力比较大的话,多数能搞死宿主机。
publicclassThreadLeakDemo{
publicstaticvoidstart(){
while(true){
newForeverThread().start();
}
}
}
publicclassForeverThreadextendsThread{
@Override
publicvoidrun(){
//Putthethreadtosleepforever sotheydon'tdie.
while(true){
try{
//Sleepingfor10minutesrepeatedly
Thread.sleep(10*60*1000);
}catch(Exceptione){}
}
}
}
这是暴力啊,这和每个请求创建一个线程,或者创建一个线程池的后果是一样的。xjjdog这里还有两篇关联的线程泄漏文章。
- 强烈反对使用Spring封装的多线程类!
- 夺命故障!炸出了投资人!
java.lang.OutOfMemoryError: unable to create new native thread
5.死锁死锁代码一般不会发生,但一旦发生还是非常严重的,相关的业务可能就跑不动了。
publicclassDeadLockDemo{
publicstaticvoidstart(){
newThreadA().start();
newThreadB().start();
}
}
publicclassThreadAextendsThread{
@Override
publicvoidrun(){
CoolObject.method1();
}
}
publicclassThreadBextendsThread{
@Override
publicvoidrun(){
HotObject.method2();
}
}
publicclassCoolObject{
publicstaticsynchronizedvoidmethod1(){
try{
//Sleepfor10seconds
Thread.sleep(10*1000);
}catch(Exceptione){}
HotObject.method2();
}
}
publicclassHotObject{
publicstaticsynchronizedvoidmethod2(){
try{
//Sleepfor10seconds
Thread.sleep(10*1000);
}catch(Exceptione){}
CoolObject.method1();
}
}
死锁属于比较严重的一种情况,jstack 会以明显的信息进行提示。当然,关于线程的 dump,也有一些线上分析工具可以使用。比如fastthread,但也需要你先了解这些情况发生的意义。
6.栈溢出栈溢出不会造成 JVM 进程死亡,危害“相对较小”。下面是一个简单的模拟栈溢出的代码,只需要递归调用就可以了。
publicclassStackOverflowDemo{
publicvoidstart(){
start();
}
}
通过 -Xss 参数可以设置虚拟机栈的大小。比如下面的命令就是设置栈大小为 128K。
-Xss128K
如果你的应用经常发生这种情况,可以试着调大这个值。但一般都是因为程序错误引起的,最好检查一下自己的代码。
7.Blocked线程BLOCKED是一个比较严重的线程状态,当后端的服务处理时间非常长,请求的线程就会进入等待状态。这时候通过jstack来获取堆栈,就会发现线程处于阻塞状态。它阻塞在对锁的获取上(wating to lock)
publicclassBlockedAppDemo{
publicstaticvoidstart(){
for(intcounter=0;counter<10; counter){
//Launch10threads.
newAppThread().start();
}
}
}
publicclassAppThreadextendsThread{
@Override
publicvoidrun(){
AppObject.getSomething();
}
}
publicclassAppObject{
publicstaticsynchronizedvoidgetSomething(){
while(true){
try{
Thread.sleep(10*60*1000);
}catch(Exceptione){}
}
}
}
一旦频繁发生这种情况,就证明你的程序相应太慢了。如果CPU资源还有剩余,可以尝试着增加请求的线程数,比如tomcat的最大线程数。
End以上就是对于Java常见故障的几段小代码分析,大部分的故障都逃不出这些场景。故障的排查通常都非常耗费精力,而且你得有线上权限。怎样做一些好用的工具,把这些复杂性屏蔽在后面,才是我们所想要的。
作者简介:小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。
推荐阅读:
1. 玩转Linux
2. 什么味道专辑
3. 蓝牙如梦
4. 杀机!
5. 失联的架构师,只留下一段脚本
6. 架构师写的BUG,非比寻常
7. 有些程序员,本质是一群羊!
小姐姐味道 不羡鸳鸯不羡仙,一行代码调半天 335篇原创内容 -->