美文网首页
Java虚拟机-JVM实战与调优!

Java虚拟机-JVM实战与调优!

作者: Coding测试 | 来源:发表于2021-06-05 21:32 被阅读0次

1、Java虚拟机可能带来的问题

先列举一下大家在日常工作可能已经遇到的问题:

  • 正在运行的 Java 进程,可能突然就 OOM 内存溢出了
  • 线上系统产生卡顿,CPU 疯狂运转,GC 时间飙升,严重影响了服务响应时间
  • 面对一堆 JVM 的参数无从下手,错失了性能提升的可能,或者因为某个参数的错误配置,产生了尴尬的负面效果
  • 想要了解线上应用的垃圾回收状况,却不知从何开始,服务监控状况无法掌控
  • JVM出现fullGC很频繁,怎么去线上排期问题?
  • 类加载为什么要使用双亲委派模式,有没有什么场景打破了这个模式?

2、实战记录

示例:


package com.xes.jvm.gc;
 
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
/**
 *
 */
 
public class GCCase {
 
    private static class CardInfo {
        BigDecimal price = new BigDecimal(0.0);
        String name = "张三";
        int age = 5;
        Date birthdate = new Date();
 
        public void m() {}
    }
 
    private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
            new ThreadPoolExecutor.DiscardOldestPolicy());
 
    public static void main(String[] args) throws Exception {
        executor.setMaximumPoolSize(50);
 
        for (;;){
            modelFit();
            Thread.sleep(100);
        }
    }
 
    private static void modelFit(){
        List<CardInfo> taskList = getAllCardInfo();
        taskList.forEach(info -> {
            // do something
            executor.scheduleWithFixedDelay(() -> {
                //do sth with info
                info.m();
 
            }, 2, 3, TimeUnit.SECONDS);
        });
    }
 
    private static List<CardInfo> getAllCardInfo(){
        List<CardInfo> taskList = new ArrayList<>();
 
        for (int i = 0; i < 100; i++) {
            CardInfo ci = new CardInfo();
            taskList.add(ci);
        }
 
        return taskList;
    }
}

在 Linux 服务跑起来

java -cp jvm-optimization-1.0-SNAPSHOT.jar -Xms200M -Xmx200M -
XX:+PrintGC com.xes.jvm.gc.GCCase -> catalina.out &

常见配置汇总
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

CPU 占用过高排查思路
先通过 top 命令找到消耗 cpu 很高的进程 id 假设是 26246
top 命令是我们在 Linux 下最常用的命令之一,它可以实时显示正在执行进程的 CPU 使用率、内存使用率以及系统负载等信息。其中上半部分显示的是系统的统计信息,下半部分显示的是进程的使用率统计信息。

image.gif

通过top -p PID的方式,查看指定进程的消耗情况,再使用 -H的命令,找到消耗资源过高的线程信息


图片

定位到消耗较高的线程是26248,转换十六进制后是0x6688,通过jstack命名继续查看java进程的堆栈信息,发现消耗大量资源的线程是VM Thread


图片

继续通过jstat -gc 26246命令,发现FGC的频次非常的高,最终定位到是因为FULL GC导致服务器CPU资源被耗尽


图片 那么这个JAVA进程为什么会频繁进行FULL GC?我们可以使用jmap命令查看jvm内存使用情况,通过快照发现占用存储空间排名前几的对象信息: 图片

3、常见问题分析

超大对象
代码中创建了很多大对象 , 且一直因为被引用不能被回收,这些大对象会进入老年代,导致内存一直被占用,很容易引发 GC 甚至是 OOM
超过预期访问量
通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。
比如如果一个系统高峰期的内存需求需要 2 个 G 的堆空间,但是堆空间设置比较小,导致内存不够,导致 JVM 发起频繁的 GC 甚至OOM过多使用 Finalizer
过度使用终结器(Finalizer),对象没有立即被 GC,Finalizer 线程会和我们的主线程进行竞争,不过由于它的优先级较低,获取到的 CPU 时间较少,因此它永远也赶不上主线程的步伐,程序消耗了所有的可用资源,最后抛出 OutOfMemoryError 异常。内存泄漏
1.大量对象引用没有释放,JVM 无法对其自动回收。
2.长生命周期的对象持有短生命周期对象的引用
例如将 ArrayList 设置为静态变量,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏
3.连接未关闭
如数据库连接、网络连接和 IO 连接等,只有连接被关闭后,垃圾回收器才会回收对应的对象。

package com.xes.jvm.gc;

public class GCCase2 implements Runnable{
    public int flag = 1;
    static Object o1 = new Object(), o2 = new Object();

    @Override
    public void run() {
        System.out.println("flag=" + flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("1");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        GCCase2 td1 = new GCCase2();
        GCCase2 td2 = new GCCase2();
        td1.flag = 1;
        td2.flag = 0;
        new Thread(td1).start();
        new Thread(td2).start();

    }
}

通过jps 或是 top命令,找到对应的java进程,查看线程信息时,发现状态都是sleep


再根据jstack -l 27370,发现提示发现死锁,分析死锁提示发现在GCCase2的18行和30行都在等待锁释放。


至此已定位到死锁的位置,后面就可以进一步优化了!
`

相关文章

网友评论

      本文标题:Java虚拟机-JVM实战与调优!

      本文链接:https://www.haomeiwen.com/subject/edbdsltx.html