没有最好的垃圾收集器,只有最合适的。所以需要根据自己的业务场景选择适合自己的收集器。 如果你的系统追求“响应时间”那么你完全可以选择PN+CMS,G1垃圾收集器,提升用户体验。 如果追求“吞吐量”可以选择并发类型收集器PS+PO。
吞吐量指:用户代码执行时间/用户代码执行时间+垃圾回收时间 。运用场景:大数据运算,数据挖掘等 响应时间:b/s模式用户的响应时间,又指FGC的STW的停顿时间
1.硬件上的升级,最直接的可以升级CPU,CPU核数越高越好 2.JVM内存设置,提前预估设置年轻代,老年代的大小。当然需要经过大量的测试。 3.开启GC日志,记录每天GC发生情况,当出现问题时好定位。建议划分日志的大小,将一个大的日志文件分成几个小的来存,这样在查找的时间方便。或者按每天生成一个,这种需要考虑自己线上的业务量,如果每天日志几个G也不好查找。 4.分析GC日志,有条件的可以使用Jconsole,Jvisualvm,jhat,jprofiler等可视化监控工具,没有条件的通过DOS命令如jps,jstack,jmap都是非常常用的命令
这里举几个列子来说明(非正常生产配置): 1.某中型在线图书网站,原来的服务器32位,内存32G,8核CPU,在高峰时间用户响应缓慢,后升级到64位,内存64G结果更加卡顿了,反而效率比没升级之前还要低。 首先先来分析在没升级前为什么会慢? 高峰时期大量用户涌入,数据从磁盘load到内存,导致内存不足频繁GC,发生STW,导致响应变慢 为什么升级后更加卡了? 升级内存后,虽然内存变大,只是发生STW的时间后延了,但是一旦发生GC,则GC扫描的内存更大,STW更长了 这是你就要看你的JVM用的是哪种收集器了,一般出现这种情况应该是用了并发型的PS+PO,这种内存越大发生 GC时相对的STW就越长,所以你需要换成CMS+PN或者是G1来处理,用户线程和垃圾收集线程并发进行,这样就 提高了响应时间
2.运维人员反应线上服务器CPU经常100% 这里提供一下查找思路: 1.使用top命令找出占用cpu最大的那个进程 2.使用top -Hq查找进程中线程的占用情况 3.使用jstack查看各线程的运行情况,一般查看是否有线程死锁,重点关注线程的 WAITING BLOCKED状态,找出 长期占用的线程 4.使用jmap命令有条件的dump出日志,没有条件的使用 jmap -histo等命令可以查找出对象实例占用情况 然后在分析。
像比较常见的OOM问题: 1.分配内存大小并打印明细的GC日志,一般将初始内存和最大内存设置为同一个值,可以使用一下命令
java -Xms200M -Xmx200M -XX:+PrintGCDetails2.使用top指令查看进程
toppid为进程唯一id,除了pid,%cpu,%mem 分别为占用cpu的大小,内存的大小
3.top -Hp 观察进程中的线程,哪个线程CPU和内存占比高
top -Hp pid4.jps定位具体java进程,jstack 定位线程状况,重点关注:WAITING BLOCKED
我这里模拟了一段简单死锁代码
public class Tets { public synchronized void print(){ List list=new ArrayList(); while(true){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } list.add(new Byte[1024*1024*1]); System.out.println("死锁了"); } } public static void main(String[] args) { Tets t=new Tets(); for (int i = 0; i < 100; i++) { new Thread(()->{ t.print(); }).start(); } } }使用jps 查找 进程号 接着使用 jstack 19308 获取进程中的线程状态,我们可以看到有大量的线程BCLOCKED阻塞住了,被的线程号为[0x000000002967f000] 阻塞了,那么我们可以分析一定是[0x000000002967f000]这个线程发生了问题,那我们就去找这个[0x000000002967f000]线程,发现这个线程的状态一直在RUNNABLE,那么说明[0x000000002967f000]线程发生了死锁 4.线程的名称(尤其是线程池)都要写有意义的名称 定义有意义的线程name方便后续问题的定位,比如你使用线程池的话,一般不建议使用juc提供的4中线程池,创建线程池用
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());怎么样自定义线程池里的线程名称?(自定义ThreadFactory) 各个参数在这里不解释是什么意思了,那么关注第6个参数创建ThreadFactory,一般我们需要直接定义ThreadFactory的实现,并重写newThread方法创建线程。
5.使用jmap 查看产生了多小对象
jmap - histo 4655 | head -20取前20条,这里可以列出具体是哪个对象,当然条件允许也可以dump出文件 (一般线上不建议dump,dump很占资源,可以会造成卡死)
jmap -dump:format=b,file=xxx pid :当然这里只是列出了查找问题的思路,实际上的线上定位问题远比这个要复杂的多,但是基本上也离不开这几种方法,只要掌握这些常用的指定根据自己遇到的问题应该很快能够定位