Java jvm 调优你应该知道的基础知识

原创
03/09 23:51
阅读数 447

JVM调优的原则:在极短的停顿时间内,尽量提升吞吐量,降低GC次数。

主要包含以下内容:

1、简述JDK 、 JRE 、 JVM之间的关系?
2、简述类加载机制的作用和过程?
3、运行时数据区分几块?说说你对每块区域的理解。
4、结合Eden、S0、S1和Old区,描述一下一个对象创建的过程
5、怎样确定一个对象为垃圾?
6、常用的垃圾回收算法有哪些?并且有什么优缺点
7、简述吞吐量和停顿时间的理解
8、常见的JDK命令和工具有哪些?简述一下它们的作用
9、JVM 垃圾收集器有几种?简述其区别

1、简述JDK 、 JRE 、 JVM之间的关系?

JDK是 Java 语言的软件开发工具包。

JRE即Java运行环境,包含Java系统类库**,**JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具。

JVM是Java虚拟机的缩写,它是一个虚拟出来的计算机,通过JVM实现了Java 跨平台的特性,所有的Java编译之后的class文件都通过Jvm来执行。

如下图所示:

2、简述类加载机制的作用和过程?

类加载主要是将编译后的class文件装载到 java运行时的内存中, 将其放在运行时数据区域的方法区内,完成准备状态,可供系统使用。

类加载主要分为三步:加载、链接、初始化,具体如下:

加载:

  1. 通过一个类的全限定名来获取其定义的二进制字节流;

  2. 将这个字节流所代表的静态存储结构转为方法区的运行时数据结构;

  3. 在Java 堆中生成一个代表这个类的java.lang.Class 对象,作为方法区中这些数据的访问入口。

链接:

1)验证:确保被加载类的正确性;

  1. 准备:为类的静态变量分配内存,并将其初始化为默认值 ;

  2. 解析:把类中的符号引用转换为直接引用

初始化:

1)声明类变量是指定初始值;

  1. 使用静态代码块为类变量指定初始值;

3、运行时数据区分几块?说说你对每块区域的理解。

运行时数据区主要分五块,本别是:方法区、堆、虚拟机栈、本地方法栈、程序计数器,如下图:

方法区: 用来存储被虚拟机加载的类信息,常量,静态变量 。

: 为对象实例分配好内存,用来存放对象实例。

虚拟机栈: 虚拟机栈描述的是Java方法执行的内存模型,素有的方法调用是以栈帧的形式保存起来的。

本地方法栈:主要为本地方法服务,保存了方法的局部变量(class字节码中,在静态方法中,方法栈第一个数据是保存的第一个参数,非静态方法,方法栈中第一个数据保存的是当前对象的引用)

程序计数器: 主要是记录当前线程执行的字节码的行号指示器,且通过线程轮流切换并且分配处理器执行时间来实现JVM的多线程。

4、结合Eden、S0、S1和Old区,描述一下一个对象创建的过程

    Java JVM 堆的 内存模型中主要包含 Eden、S0、S1 和 Old取,其中 Eden、S0、S1 三个区域合称为为新生代(占用1/3堆空间),Old 取为老年代(占用2/3堆空间),如下图所示:

                           

在新生代主要存放生命周期较短,频繁生成和回收的对象,老年代主要存放生命周期较长的对象,新生代每执行一次GC,没有被回收的对象的年龄都加一,当对象的年龄达到15时,对象会从新生代转移老年代中。

对于新生代,对象生成时会被分配到Eden区,如果Eden区已经没有空间,则会执行 Young GC,新生代中所有无效数据会被回收,回收之后Eden的数据会被转移至一个空白的 S0区或S1区(S0、S1至少有一个空白区域),另一个有数据的S区也会被复制到空白的S区,此时Eden和另一个S区会被清空,重新接收数据生成,此时如果空白的S区空间不够,会向老年代借用一小部分空间用于存放数据。

5、怎样确定一个对象为垃圾?

        确定一个对象为垃圾,主要有两种算法进行判断,分别为: 引用计数算法 、可达性分析法。

        引用计数算法

        引用计数算法就是在对象中添加了一个引用计数器,当有地方引用这个对象时,引用计数器的值就加1,当引用失效的时候,引用计数器的值就减1。当引用计数器的值为0时,jvm就开始回收这个对象。

        这种方法虽然很简单、高效,但是JVM一般不会选择这个方法,因为这个方法会出现一个问题:当对象之间相互指向时,两个对象的引用计数器的值都会加1,而由于两个对象时相互指向,所以引用不会失效,这样JVM就无法回收。

        可达性分析法

         针对引用计数算法的BUG,JVM采用了另一种方法:定义一个名为"GC Roots"的对象作为起始点,这个"GC Roots"可以有多个,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,即可以进行垃圾回收。

         GC Roots对象一般包括有:1.虚拟机栈(栈帧中本地变量表)中引用的对象;2.方法区中类静态属性引用的对象;3.方法区中常量引用的对象;4.本地方法栈中JNI(Native方法)引用的对象。

6、常用的垃圾回收算法有哪些?并且有什么优缺点

        主要的垃圾回收算法主要有如下几种:

        引用计数算法  见上面第五点

        标记清除法

                这个方法是将垃圾回收分成了两个阶段:标记阶段和清除阶段。

                在标记阶段,通过跟对象,标记所有从跟节点开始的可达的对象,那么未标记的对象就是未被引用的垃圾对象。

                在清除阶段,清除掉所以的未被标记的对象。

                这个方法的缺点是,垃圾回收后可能存在大量的磁盘碎片,准确的说是内存碎片。因为对象所占用的地址空间是固定的。

                对于这个算法还有改进的算法,就是我后面要说的标记清楚整理法。

        标记整理清除法(老年代中使用)

                在标记清楚的基础上做了一个改进,可以说这个算法分为三个阶段:标记阶段,整理阶段,清除阶段。标记阶段和清除阶段不变,只不过增加了一个整理阶段,就是在做完标记阶段后,将这些标记过的对象集中放到一起,这样再去清除,将不会产生磁盘碎片。但是我们也要注意到几个问题,整理阶段占用了系统的消耗,并且如果标记对象过多的话,损耗可能会很大,在标记对象相对较少的时候,效率较高。

        复制算法 (新生代中使用)

                核心思想是将内存空间分成两块,同一时刻只使用其中的一块,在垃圾回收时将正在使用的内存中的存活的对象复制到未使用的内存中,然后清除正在使用的内存块中所有的对象,然后把未使用的内存块变成正在使用的内存块,把原来使用的内存块变成未使用的内存块。很明显如果存活对象较多的话,算法效率会比较差,并且这样会使内存的空间折半,但是这种方法也不会产生内存碎片。

                

7、简述吞吐量和停顿时间的理解

        衡量一个Java 垃圾收集器的优秀与否,吞吐量和停顿时间是非常重要的两个标准,一般来说 吞吐量越高算法越好,暂停时间越短算法越好 。

        吞吐量

        所谓的吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即: 吞吐量=运行用户代码时间 /  (运行用户代码时间+垃圾收集时间),虚拟机总共运行100分钟,其中垃圾收集花了1分钟,那么吞吐量就是99%。

        停顿时间

        在JVM执行垃圾回收时,会将所有业务线程暂停,在暂停的极短一段时间那进行垃圾回收的一些列步骤,暂停的这段时间称为 停顿时间。

        停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验;而高吞吐量则适合高效利用CPU时间的应用,例如运算任务和交互较少的应用。


8、常见的JDK命令和工具有哪些?简述一下它们的作用

常用JDK命令行工具如下:

名称

作用

jps 显示系统中所有的Hotspot虚拟机进程
jstat 用于收集HotSpot虚拟机各方面的运行数据
jinfo 显示虚拟机配置信息
jmap 生成虚拟机的内存转储快照(heapdump文件)
jhat 用于分析heapdump文件,会建立一个HTTP/HTML服务器,用户可以在浏览器上查看分析结果
jstack 显示虚拟机的线程快照
jConsole JMX的可视化管理工具

 

9、JVM 垃圾收集器有几种?简述其区别

                                 

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默认垃圾收集器G1

目前主流的垃圾收集器主要有七种,他们要么使用标记清除,要么使用标记整理算法。(上图没有包含JDK11的垃圾收集器:ZGC),具体详细如下:

1、Serial 收集器

        Serial 是一个单线程收集器,是JDK1.3之前的主要收集器,Serial 收集器适用于新生代,通常搭配 Serial Old 收集器进行老年代的垃圾回收。在单线程是使用效率非常高,在手机垃圾过程中,会暂停用户所有业务线程(stw:stop the world)。

        优点:单线程效率高,缺点:不支持多线程,造成用户业务停顿。

2、 ParNew收集器

        ParNew收集器是第一个支持多线程的垃圾收集器,利用了多线程优势,性能比Serial收集器 明显提高,但是依然是中断所有业务代码,只是多线程进行垃圾回收(垃圾回收线程与业务线程没有并行执行)。

        优点:吞吐量大,缺点:停顿时间依然比较久。

3、CMS 收集器

     首次出现在JDK1.5,首次支持了业务线程与垃圾回收线程的并行执行,但是并没有做到全程一起执行,只是在标记阶段并发,如下图:

                  

        初始标记时间非常短(可达性算法),不用开多线程,开多线程耗费的时间成本比单线程执行更高,所以并不会多线程执行,后面并发标记阶段用户业务线程并行执行 又会产生垃圾此时会产生浮动垃圾,本次回收无法会收到。

        初始标记与并行标记会使用不同的标记算法,确保回收更加准确,之后执行清理阶段是并行的。

        优点:并行停顿时间更短,缺点:产生浮动垃圾,并行清理时使用的是标记清除算法,会产生大量的空间碎片。

 

4、G1 收集器

       JDK1.7中出现,JDK1.8中推荐使用,JDK1.9 默认收集器。

       G1收集器相比CMS,优化了内存空间不连续的问题,整个堆区使用 Region区域,颠覆了以前的 EDen、S0、S1、Old 区域,逻辑上依然有这些空间,但是并不在一起了,如下图:

                                                

每个区域角色可以随着垃圾回收进行切换,更加利于空间连续,在筛选回收时可以更加优先存活对象较少的区域,回收时间可以选择性可控了。

 

5、ZG1收集器

首次出现在JDK11,ZG1垃圾收集器更加优化了停顿时间,单词垃圾回收可以降低在10毫秒以下。

 

 

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部