Java项目运维总结记录

一、常见故障

1.1、Java项目CPU耗用突增100%分析定位

1)执行top命令确认当前占用cpu的的java进程;以下是一些快捷键:

?:显示在top当中可以输入的命令 P:以CPU的使用资源排序显示 M:以内存的使用资源排序显示 N:以pid排序显示 T:由进程使用的时间累计排序显示 k:给某一个pid一个信号。可以用来杀死进程 r:给某个pid重新定制一个nice值(即优先级) q:退出top(用ctrl+c也可以退出top)。

2)确认异常进程的具体线程:上述界面按H键或执行top -Hp java_Pid,然后记录异常线程的pid;

另外:ps -eLf | grep java也可查看 //e是显示全部进程,L是显示线程,f全格式输出;其中LWP( (light weight process, 轻量级进程,即线程标识符,NLWP为线程数量)列即线程的pid;

或ps -mp pid号 -o THREAD,tid,lwp,nlwp,time,rss,size,%mem //查看LWP

或htop命令,按t(显示进程线程嵌套关系)和H(显示线程) ,然后F4过滤进程名;

jstack命令生成的thread dump信息包含了JVM中所有存活的线程,为了后续追踪查看该线程的堆栈情况,分析指定线程,必须找出对应线程的调用栈,在thread dump中每个线程都有一个nid(native thread id,每一个都对应线程的tid(即top里看到的线程pid,又称java线程的内存地址),可在/proc/pid/task/下查看对应文件,但这里显示的10进制的,而nid显示为16进制的,需转换以下来对应起来),找到对应的nid即可,另在dump信息中,线程主要呈现这3种状态:RUNNABLE,线程处于执行中;BLOCKED,线程被阻塞;WAITING,线程正在等待

3)查看线程的内存:printf ‘%x’ 线程_pid,可得到线程的内存指针或地址,记录该16进制值a;

4)跟踪异常进程:jstack java_Pid > my.log //查看该线程当前的堆栈状态

5)查看上述输出日志:less my.log|grep a //通过上述的线程内存地址确认线程相关的堆栈调用情况,比如下图:
Java项目运维总结记录
通过nid16进制246定位到该位置,然后再com部分可看到对应的业务堆栈调用,该线程一直在执行JstackCase类第33行的calculate方法,得到这个信息,就可以检查对应的代码是否有问题。

堆栈,消息模式为FILO(先进后出),某线程堆栈调用(实际就是相关函数调用的时候参数和局部变量的入栈和出栈等),我们知道异常堆栈的顺序应该是从下往上看,异常是一层一层地往外抛出,直至抛出到最外层(即没有catch为止)。最上面的堆栈异常只是真正异常的一个外层表现而已,异常的根源还是要看堆栈最底部的信息。

通过堆栈异常信息中的at字段可看到直接导致发生异常的包名-类名-方法名-代码行数

6)异常堆栈产生的流程:

示例:

package com.bsx.test;

public class TestException {
    public static void main(String[] args) {
        TestException exception = new TestException();
        exception.m1();
    }

    public void m1() {
        m2();
    }

    public void m2() {
        m3();
    }

    public void m3() {
        String name = null;
        System.out.println(name.length());
    }

}

执行后,异常输出如下:

Exception in thread "main" java.lang.NullPointerException
	at com.bsx.test.TestException.m3(TestException.java:22)
	at com.bsx.test.TestException.m2(TestException.java:17)
	at com.bsx.test.TestException.m1(TestException.java:13)
	at com.bsx.test.TestException.main(TestException.java:9)

Java语言的异常类Exception包含着异常的全部信息。从代码及异常输出我们可以知道,这个错误日志输出的顺序跟调用顺序是相反的,我们知道java的方法在执行的时候是在虚拟机栈中执行的,每执行一个方法就会新建一个栈帧然后压入到虚拟机栈中。这是一个后进先出的结构,所以报错的时候也是从被调用者最开始报错,即M3函数先报错,但是是先执行的main函数,然后调用者依次报错,所以打印错误时的顺序也是最从调用报错的位置在最上面,调用者依次向后排。由此我们可以得出堆栈异常信息输出结论:上面报错,下面(调用)跟随。

因此,我们可认为最根本的报错位置在最上面。大部分情况下,最上方的报错信息就是我们代码出错的位置。但是有时最上方的日志不一定是我们自己的代码,比如当你的代码调用了一些三方jar包的代码。但是这并不影响我们去定位问题,我们还是根据最上面报错,来逐步定位问题,因真正报错的位置还是在上面,它就是异常抛出的点。那么我们只需要从上往下依次找我们自己的代码即可。第一个找到的我们的代码位置就是我们代码中引发报错的位置。有时候有些报错信息很明显,我们可以根据报错信息来直接定位到问题症结。有时候报错信息并不能很明确的指明报错原因,这时候,我们就可以在这个精确的位置打上断点来调试一下。

示例2:

javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is:   //该异常在javax.naming包里面,异常的继承结为:java.lang.Exception---->javax.naming.NamingException--->javax.naming.ServiceUnavailableException
其中,javax.naming.NamingException:此异常是 Context 和 DirContext 接口中的操作抛出的所有异常的超类。失败的特性由子类的名称描述。此异常捕获指出操作失败处的信息,比如解析最后进行到的位置。javax.naming.ServiceUnavailableException:当试图与目录或命名服务通信,而该服务不可用时,抛出此异常。该服务可能因为各种原因而不可用。例如,服务器可能太忙而无法为请求提供服务,或者服务器可能没有为向某些请求提供服务而注册等等。最原始的异常是java.rmi.ConnectException.相关的描述是连接被拒绝。而这个异常中嵌套的异常是java.net.ConnectException 连接被拒绝。java.rmi.ConnectException的父类为java.rmi.RemoteException;RemoteException 是许多与通信相关的异常的通用超类,这些异常可能会在执行远程方法调用期间发生。

远程接口(扩展 java.rmi.Remote 的接口)的每个方法必须在其 throws 子句中列出 RemoteException。java.rmi.Connection:如果拒绝远程主机对连接的远程方法调用,则抛出 ConnectException。

######
java.net.ConnectException: Connection refused: connect]

at com.sun.jndi.rmi.registry.RegistryContext.rebind(RegistryContext.java:142)

at com.sun.jndi.toolkit.url.GenericURLContext.rebind(GenericURLContext.java:231)

at javax.naming.InitialContext.rebind(InitialContext.java:408)

at com.net.rmi.hello.SimpleServer.main(SimpleServer.java:13)

Caused by: java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is:

java.net.ConnectException: Connection refused: connect

at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:601)

at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:198)

at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:184)

at sun.rmi.server.UnicastRef.newCall(UnicastRef.java:322)

at sun.rmi.registry.RegistryImpl_Stub.rebind(Unknown Source)

at com.sun.jndi.rmi.registry.RegistryContext.rebind(RegistryContext.java:140)

... 3 more

Caused by: java.net.ConnectException: Connection refused: connect

at java.net.PlainSocketImpl.socketConnect(Native Method)

at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)

at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)

at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)

at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)

at java.net.Socket.connect(Socket.java:519)

at java.net.Socket.connect(Socket.java:469)

at java.net.Socket.(Socket.java:366)

at java.net.Socket.(Socket.java:180)

at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(RMIDirectSocketFactory.java:22)

at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(RMIMasterSocketFactory.java:128)

at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:595)

1.2、堆栈异常

eg1:多线程竞争synchronized锁
Java项目运维总结记录
如上图所示,线程1获取到锁,处于RUNNABLE状态,线程2处于BLOCK状态

1、locked <0x000000076bf62208>说明线程1对地址为0x000000076bf62208对象进行了加锁;

2、waiting to lock <0x000000076bf62208> 说明线程2在等待地址为0x000000076bf62208对象上的锁;

3、waiting for monitor entry [0x000000001e21f000]说明线程1是通过synchronized关键字进入了监视器的临界区,并处于"Entry Set"队列,等待monitor

eg2:
Java项目运维总结记录
上图中,线程1和2都处于WAITING状态

1、线程1和2都是先locked <0x000000076bf62500>,再waiting on <0x000000076bf62500>,之所以先锁再等同一个对象,是因为wait方法需要先通过synchronized获得该地址对象的monitor;

2、waiting on <0x000000076bf62500>说明线程执行了wait方法之后,释放了monitor,进入到"Wait Set"队列,等待其它线程执行地址为0x000000076bf62500对象的notify方法,并唤醒自己。

1.3、

上一篇:统计图表类库--libchart使用简介


下一篇:Java RMI学习与解读(二)