JVM编程语言-JVM系列new

深入浅出JVM

2021-02-03  本文已影响0人  OOMNPE

JVM 是 Java 程序运行基础,面试时一定会遇到 JVM 相关的题。本文会先对面试中 JVM 的考察点进行汇总介绍。然后对 JVM 内存模型、Java 的类加载机制、常用的 GC 算法这三个知识点进行详细讲解。

1. JVM知识点汇总

JVM 知识点汇总

如上图所示,JVM 知识点有 6 个大方向,其中,内存模型、类加载机制、GC 垃圾回收是比较重点的内容。性能调优部分偏重实际应用,重点突出实践能力。编译器优化和执行模式部分偏重理论基础,主要掌握知识点。

在开始下文前,看下你是否能够回答以下知识点:

2. JVM 内存模型

JVM内存模型主要指运行时的数据区,包括如下5个部分


JVM内存模型

3. 类加载机制

类的加载过程是指将编译好的class类文件的字节码读入到内存中,将其存在方法区并创建对应的Class对象;类的加载分为加载、链接、初始化,其中链接又包含验证、准备、解析三步,如图所示


JVM类的加载过程

3.1 类加载器

如下图,JVM自带的三个类加载器分别是:BootStrap启动类加载器、扩展类加载器、应用加载器;以及分别对应的加载目录;


JVM类加载模式

双亲委派

java的类加载使用双亲委派模式,即一个类加载器在加载类时,会递归的委托给父类加载器去执行,直至顶层的启动类加载器,如果父类加载器能够加载,则返回成功,否则子加载器才会自己尝试加载;
对于两个不同的类加载器(自定义的、没有继承关系),加载同一个类,会导致两个类不等;

双亲委派的好处

  1. 避免重复加载
  2. 防止对JDK核心类进行篡改,比如String.class由启动类加载器加载,如果想篡改String类,那么不会生效;

4. GC

4.1 对象已死?

判定对象的存活都与“引用”有关,有两种方法去判断一个对象已经“死了”;

再谈引用

如果一个对象只被定义为“被引用”或者“未被引用”两种状态,那么对于一些“食之无味,弃之可惜”的对象就无能无力;由此产生了四种引用类型:

4.2 分代回收

JVM的堆内存被分代管理,包括新生代和老年代,这样做主要是为了兼顾垃圾收集的时间开销和内存的空间有效利用;大部分对象很快就不再使用;

GC的执行过程

新生代区分为3个部分:1个eden区、2个Survivor区(from和to,复制算法),新创建的对象都会被分到Eden区,这些对象经过一次Minor GC后,如果仍然存活,则会被分配到Survivor区,然后在Survivor区每熬过一次Minor GC后年龄就会增长一岁,达到一定年龄后,就被移动到老年代中。

4.3 垃圾回收算法

4.4 常见垃圾收集器

JVM 中提供的年轻代垃圾收集器 Serial、ParNew、Parallel Scavenge 都是复制算法,而 CMS、G1、ZGC 都属于标记清除算法。

JDK版本默认垃圾收集器

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9、10、11 默认垃圾收集器G1
-XX:+PrintCommandLineFlagsjvm参数可查看默认设置收集器类型
-XX:+PrintGCDetails亦可通过打印的GC日志的新生代、老年代名称判断

CMS

JDK1.7之前最主流的垃圾回收器;使用标记-清除算法,并发收集停顿小;


CMS

三色标记算法

G1

G1取消了堆中年轻代与老年代的物理划分,但它依然属于分代收集器;G1算法将堆划分为若干Region区域,一部分作为新生代一部分作为老年代;


G1算法

ZGC

JDK11提供的高效垃圾回收算法,针对大堆内存设计;主要特点:着色指针、读屏障、并发处理、基于Region、内存压缩(整理)


ZGC算法

5. JVM调优

5.1 编译优化

5.1.1 方法内联

调用方法要经历压栈和出栈,这会带来一定的时间和空间方面开销,那么对于那些代码体量不大,又频繁调用的方法,这个时间和空间的消耗会很大;
方法内联的优化就是将那些代码体量小的方法代码复制到发起调用的方法之中,避免真实调用;

热点方法能提高系统性能,提高方法内联的几种方式

5.1.2 逃逸分析

逃逸分析(Escape Analysis)是判断一个对象是否被外部方法引用或外部线程访问的分析技术,编译器会根据逃逸分析的结果对代码进行优化.

栈上分配

java对象默认分配在堆上,这会带来垃圾回收的时间和空间消耗,如果一个对象只在方法类使用(未发生逃逸),比如方法类的局部变量,这个时候将对象分配到线程栈上,随着栈空间的回收而回收,带来性能提升;
开启方式:-XX:+DoEscapeAnalysis(JVM默认是开启的)
关闭方式:-XX:-DoEscapeAnalysis

锁消除

当一个线程安全容器,比如StringBuffer,在未发生逃逸时,JIT编译(运行时)会自动进行Synchronized锁消除;比如如下代码,StringBuffer和StringBuilder的性能差别不大

     public static String getString(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
    }

开启方式:-XX:+EliminateLocks

标量替换

5.2 GC调优

5.2.1 降低Minor GC频率

5.2.2 降低Full GC频率

5.2.3 选择合适的GC回收器

5.3 内存分配及参数调优

根据实际情况设置JVM的启动参数,常用的JVM优化参数:

配置参数 功能
-Xms 初始化堆大小,如:-Xms256m,一般和Xmx保持一样
-Xmx 最大堆大小,最好设置为容器最大内存的80%
-Xmn 新生代大小,推荐设置Xmx的3/8
-Xss 每个线程的堆栈大小,默认1M
-xx:+PrintGCDetail 打印GC日志
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/-dump.hprof 发生OOM时自动生成Dump文件

todo ...

5.4 常用的JVM分析工具

Linux 命令 - top

查看进程的CPU使用率、内存使用率、系统负载

Linux 命令 - vmstat

jstat 命令

检测java应用程序实时运行情况,包括堆内存信息及垃圾回收信息

jstack 命令

查看线程的堆栈信息

jmap 命令

查看堆内存初始化配置及堆内存的使用情况,可以把堆内存中对象的信息、对象的数量等dump到文件中,使用工具进行分析;
jmap -dump:format=b.file=/tmp/heap.hprof 28557

jps 命令

JVM Process Status Tool,显示当前java进程情况以及进程pid,可以看到启动了多少java进程(每个java进程独占一个jvm实例),类比linux的ps命令;

jconsole 命令

阿里出品 - arthas

5.5 OOM的排查

引用1
引用2

出现原因
  1. 内存中加载的数据量过于庞大,如一次性从数据库取出大量数据;
  2. 死循环
  3. JVM参数内存配置太小
  4. 根本原因:经过一次FullGC后老年代中还是满的
排查方式

1、通过IDE运行跟踪(很难找到原因)
2、保存问题现场,发生OOM时记录堆信息(导出Dump文件信息),内存溢出时jvm指令执行bat发送邮件

解决方式
  1. 增加jvm内存大小 -xmx -xms
  2. 观察gc日志,配置新生代老年代大小比例。如果程序new的比较频繁,那么新生代设置大一点
  3. 程序优化,避免死循环。

6. 引用参考

上一篇 下一篇

猜你喜欢

热点阅读