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 //通过上述的线程内存地址确认线程相关的堆栈调用情况,比如下图:
在这里插入图片描述
通过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)//com这里就是业务西瓜的调用

... 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锁
在这里插入图片描述
如上图所示,线程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:
在这里插入图片描述
上图中,线程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、Java Npe异常后无堆栈信息

异常如下:

...
java.lang.NullPointerException
...

如上所示,只有异常,但没有更详细堆栈信息,也就意味着没法定位问题代码,一把程序员第一反应可能是查看记日志的代码(用的logback),确认没有问题后,开始google和stackOverflow。

官网对此说明如下(JDK5.0的发布说明里有相关描述):

一般情况下,当异常发生时,JVM会回溯调用栈,构建异常对象(包含完整的堆栈信息)。但是出于性能考虑,如果某个方法一直抛出同样的异常,比如npe,JVM会重新编译该方法(JIT编译器),然后抛出事先创建好的异常(没有堆栈信息)。相关经验已经验证,JVM确实会进行一些优化,从而出现没有堆栈的异常。

对此可如下操作:
1)通过JVM参数禁用此优化。
JDK5.0 Release Notes表明,通过添加JVM参数禁用此优化:-XX:-OmitStackTraceInFastThrow。

2)查看更早之前的log,找到相关的异常堆栈,解决掉。如上文所说,只有某个异常出现了若干次后,才可能触发优化条件。所以,最初的日志里肯定是有完整的堆栈信息。

1.4 JVM堆内存溢出后,其他线程是否可以继续工作

当堆内存溢出发生后,系统会把相关的线程进行销毁处理,线程销毁后对应的资源就会释放,供后来的线程来使用。

可使用visualvm工具堆线程的内存消耗进行监控。它已在JDK6.0 update 7 中自带,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈,一般在JDK_HOME/bin(默认是C:\Program Files\Java\jdk1.6.0_13\bin)目录下。

VisualVM提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于 Java 技术的应用程序的详细信息。VisualVM 对 Java Development Kit (JDK) 工具所检索的 JVM 软件相关数据进行组织,并通过一种使您可以快速查看有关多个 Java 应用程序的数据的方式提供该信息。用户可以查看本地应用程序或远程主机上运行的应用程序的相关数据。此外,还可以捕获有关 JVM 软件实例的数据,并将该数据保存到本地系统,以供后期查看或与其他用户共享。
在这里插入图片描述
另外,JVisualVM 远程监控还可监控tomcat:

1)修改远程tomcat的catalina.sh配置文件,在其中增加:
JAVA_OPTS="$JAVA_OPTS
-Djava.rmi.server.hostname=192.168.122.128
-Dcom.sun.management.jmxremote.port=18999
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false"

2)打开jvisualvm,右键远程,选择添加远程主机,输入主机的名称,直接写ip:
在这里插入图片描述
在这里插入图片描述
3)右键新建的主机,选择添加JMX连接,输入在tomcat中配置的端口即可。

1.5 java多线程是如何实现的和OS有什么关系

经验证java多线程是通过调用底层的库再调用OS来实现多线程的,并不是java自己重建的多线程。

1.6 通过Netty框架实现视频弹幕功能,多人共享

1.7 线上java项目服务器内存飙升

1)top,按键shift+m,按内存排序,找到占用内存最高的进程;

2)收集堆的转储文件,执行:jmap -dump:format=b,file=heap.hprof 占用进程_pid

3)下载堆转储文件,利用eclipse memory analyzer工具将该文件打开进行文件分析;打开后会生成一个分析报告。

4)找到异常的调用堆栈占用;然后切换到直方图界面,按栈消耗排序,确认耗用大的类,以com开头的是我们的业务相关类。然后,确认业务类为什么占用内存较大,最后去代码里找相关信息,调试修正。

1.8 快速向数据库插入1000万条数据用于测试

这里,我们可以使用sysbench工具,它是一个基准测试工具,可帮助我们快速构建好数据库的数据。

1)下载安装yum源后安装
Debian/Ubuntu:
curl -s https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh | sudo bash
sudo apt -y install sysbench

RHEL/CentOS:

curl -s https://packagecloud.io/install/repositories/akopytov/sysbench/script.rpm.sh | sudo bash
sudo yum -y install sysbench

Fedora:

curl -s https://packagecloud.io/install/repositories/akopytov/sysbench/script.rpm.sh | sudo bash
sudo dnf -y install sysbench

2)或者

wget https://github.com/akopytov/sysbench/archive/1.0.zip -O "sysbench-1.0.zip"
unzip sysbench-1.0.zip
cd sysbench-1.0
yum install automake libtool –y  ## 安装依赖
./autogen.sh
./configure  #如果出现:configure: error: mysql_config executable not found,需要安装:mariadb-devel (yum install mariadb-devel 安装mysql_config) 可能还要安装mariadb-server包,yum install mariadb-server,但是我没有遇到。
export LD_LIBRARY_PATH=/usr/local/mysql/include   #这里要换成自己虚拟机中mysql路径下的include,可以用:which mysql命令查看mysql位置。
make
make install
sysbench --version  ## 验证,如果提示找不到libmysqlclient.so.20
ln -s /usr/local/mysql/lib/libmysqlclient.so.20 /usr/local/lib/libmysqlclient.so.20
##在/etc/ld.so.cnf中追加/usr/loca/lib这一行后执行:
/sbin/ldconfig -v
sysbench --version  ## 再次验证

3)测试示例

1、cpu性能测试

sysbench --test=cpu --cpu-max-prime=20000 run

cpu测试主要是进行素数的加法运算,在上面的例子中,指定了最大的素数为 20000,自己可以根据机器cpu的性能来适当调整数值。

2、线程测试

sysbench --test=threads --num-threads=64 --thread-yields=100 --thread-locks=2 run

3、磁盘IO性能测试

sysbench --test=fileio --num-threads=16 --file-total-size=3G --file-test-mode=rndrw prepare
sysbench --test=fileio --num-threads=16 --file-total-size=3G --file-test-mode=rndrw run
sysbench --test=fileio --num-threads=16 --file-total-size=3G --file-test-mode=rndrw cleanup
上述参数指定了最大创建16个线程,创建的文件总大小为3G,文件读写模式为随机读。

4、内存测试

sysbench --test=memory --memory-block-size=8k --memory-total-size=4G run
上述参数指定了本次测试整个过程是在内存中传输 4G 的数据量,每个 block 大小为 8K。

5、OLTP测试

sysbench --test=oltp --mysql-table-engine=myisam --oltp-table-size=1000000
–mysql-socket=/tmp/mysql.sock --mysql-user=test --mysql-host=localhost
–mysql-password=test prepare

4)构建1000万条数据,执行:

sysbench --db-driver=mysql --threads=4 --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=root --mysql-password=123456 --mysql-db=test --tables=1 --tables_size=1000000 oltp_read_write --db-ps-mode=disable prepare //sysbench --version ##关闭预处理;

完成后登录mysql验证数据条目数量。

1.9 千万级数据分页查询很慢,优化

1)分页:select * from table_name limit 800000,15; //每页15条

2)优化:select a.* from table_name a,(select id from table_name limit 800000,15) b where a.id=b.id;

1.10 java中有9种垃圾回收期,那什么时候使用G1垃圾处理器

1)针对大内存多处理的机器推荐采用G1垃圾收集器,eg:堆内存至少6G以上

2)超过50%的堆空间都被活动数据占用;

3)在低延迟需求的场景,即GC导致的程序暂停时间要比较少,0.5-1s左右。

4)对象在堆中分配频率或年代升级频率变化比较大,防止高并发下应用雪崩现象的场景。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羌俊恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值