ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

图解学习 JVM基本原理

2021-12-04 18:00:16  阅读:143  来源: 互联网

标签:垃圾 收集器 基本原理 GC 内存 JVM 图解 加载


图解 JVM基础知识

在java学习过程中,对java基础语法有一定了解,并且可以使用,但对java的底层运行机制还不了解,因此做这个日志,以记录JVM的学习过程

一、什么是JVM

(一)JVM定义

JVM,是java virtual machine的缩写,是一种基于计算机设备的规范

(二)为什么会出现JVM

不同平台所识别的字节码序列含义不同,例如在Windows平台下的某个指令是0101,但Linux平台下的指令可能是1010,因此不同的平台需要不同的编译器来编译代码,这使得实现相同功能的代码,需要编写多个版本,增加了编码工作量,JVM的出现,只需要编写一淘代码,即可在多个平台运行。

(三)JVM有什么作用

1、通过JVM,java实现了平台无关性,java语言在不同操作系统中无需重新编译。

2、JVM有自动垃圾回收机制,无需程序员手动进行垃圾回收,极大方便了代码编写,提高程序运行效率。

二、JVM如何实现多平台运行

(一)JVM的内存结构

JVM内存结构图

(二)类加载器 Classloader

类加载器,顾名思义,就是将java类的.class文件加载到内存中,简要过程是:将.class文件中的二进制数据读入到内存中,将其放在运行时数据区域的方法区内,然后再堆区域创建一个java.lang.Class对象

类如何进行加载呢?

1、类加载的生命周期

首先,要知道类加载的过程是什么样的,也就是生命周期是如何的。

简单说,类加载经过以下7个阶段:

加载>>连接(校验、准备、解析)>>初始化>>使用>>卸载
类加载的生命周期

(1)加载:

加载阶段,虚拟机需要完成以下三件事:

  • 通过一个类的全限定名来获取其定义的二进制流;
扩展:类的全限定名=包名+类名 如:java.lang.String
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;

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

(2)链接
  • 验证:确保被加载类的正确性

目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
包括:文件格式验证,源数据验证,字节码验证,符号引用验证。

  • 准备:为静态变量分配内存,并将其初始化为默认值

这个阶段是正式为类变量分配内存并进行初始化赋值的阶段,这些内存都在方法区中分配,需要注意2点:

1、此时内存的分配仅包括静态变量(static),不包过实例变量,实例变量不会再方法区中进行分配,而是在实力运行后堆中进行分配。
2、这里的初始赋值是指数据类型的零值(0,0L,null,false)等
  • 解析:把类中的符号引用转换为直接引用

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。

(3)初始化

初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。

  • JVM初始化类的步骤

1、假如这个类还没有被加载和连接,则程序先加载并连接该类

2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

3、假如类中有初始化语句,则系统依次执行这些初始化语句

(4)使用

类的主动使用包括以下六种:

  • 创建类的实例,也就是new的方式

  • 访问某个类或接口的静态变量,或者对该静态变量赋值

  • 调用类的静态方法

  • 反射(如Class.forName(“com.shengsiyuan.Test”))

  • 初始化某个类的子类,则其父类也会被初始化

  • Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

(5)卸载

在如下几种情况下,Java虚拟机将结束生命周期

– 执行了System.exit()方法

– 程序正常执行结束

– 程序在执行过程中遇到了异常或错误而异常终止

– 由于操作系统出现错误而导致Java虚拟机进程终止

2、类的加载器有哪些

(1)Bootstrap Classloader 启动类加载器:负责加载存放在JDK\jre\lib,或被 -Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库

(2)ExtClassLoader 扩展类加载器:加载 JDK\jre\lib\ext目录中,或者由 java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类)

(3)APPClassloader 应用类加载器:加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

(4)自定义类加载器:需要继承ClassLoader,我们只需要重写fineClass方法即可

3、类的加载机制

了解了上面的几种类加载器,还需要他们是根据什么机制进行加载的。

(1)全盘负责

当一个类的加载器负责加载某个class时,该class依赖和引用的其他class也将由该类加载器负责加载,除非显示使用另一个类加载器来加载

(2)双亲委派

当一个类加载器收到了类加载请求,它会把这个请求委派给父(parent)类加载器去完成,依次递归,因此所有的加载请求最终都被传送到顶层的启动类加载器中。只有在父类加载器无法加载该类时子类才尝试从自己类的路径中加载该类。(注意:类加载器中的父子关系并不是类继承上的父子关系,而是类加载器实例之间的关系。)

(3)缓存机制

加载过的class都会被缓存,除非缓存不存在,才会重新读取类对应的数据,同时转入缓存区。

(三)运行时数据区域 Runtime Data Area

JVM内存结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZKjclnHy-1638610457323)(/JVM/JVM内存结构/assets/jvm内存结构图.jpg “JVM内存结构图”)]

从图片可以知道,JVM运行是的数据区,大概分为5个部分

  • 堆(Heap)

而对于Java而言,每个应用都唯一对应一个JVM实例,而每一个JVM实例唯一对应一个堆。堆主要包括关键字 new的对象实例、 this指针,或者数组都放在堆中,并由应用所有的线程共享。堆由JVM的自动内存管理机制所管理,名为垃圾回收—— GC(garbage collection)。

  • 方法区(Method)

不止是存“方法”,而是存储整个 class文件的信息,JVM运行时,类加载器子系统将会提取 class文件里面的类信息,并将其存放在方法区中。例如类的名称、类的类型(枚举、类、接口)、字段、方法等等。

  • 程序计数器(PC寄存器)

为了保证程序可以连续的执行下去,需要有一些手段来确定下一条指令的地址,
它的作用可以看做是当前线程所执行的字节码的行号指示器,执行引擎会根据PC寄存器的指令地址来执行线程。

  • 虚拟机栈(Stack)

操作系统内核为某个进程或者线程建立的存储区域,它保存着一个线程中的方法的调用状态,它具有先进后出的特性。在栈中的数据的生命周期跟随着线程一起产生和消亡;在栈中,每一个方法对应一个栈帧,用于存储局部变量表、操作站、动态链接、方法出口等信息,JVM会对Java栈执行两种操作:压栈和出栈。这两种操作在执行时都是以栈帧为单位的。还有一些即时编译器编译后的代码等数据。

  • 本地方法栈

用来调用其他语言的本地方法,例如 C/C++写的本地代码, 这些方法在本地方法栈中执行,而不会在Java栈中执行。

(四)执行引擎 Execution Engine

如果想让一个Java程序运行起来、执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。简单来说,JVM中的执行引擎充当了将高级语言翻译为机器语言的译者

(五)本地接口 Native Interface

java的标准类库可能不能支持程序的所有特性,这时候有了一些其他语言,比如C语言编写的类库方法,我希望在java中用到它,就可以通过本地接口Native Interface,使用到这些方法。

三、垃圾回收GC

自动垃圾回收,是jvm中最大的一个特性,简单来说就是寻找 Java堆中的无用对象。打个比方:你的房间是一个jvm内存,你再房间里生活会制造垃圾和脏乱,你有洁癖,一有垃圾你就要拿出房间外面的垃圾桶仍,这样你就要离开房间,会影响你看书学习写作业。

  • 如何判断对象是否需要回收(是否是垃圾)

1、引用计数器算法

给每一个对象都标上一个引用计数属性,新增引用,就+1,减少引用,就-1,当计数器是0的时候,才可以回收。

但是这种方式,也有一定的问题。

(1)需要单独的字段存储计数器,这对宝贵的内存来说,是一笔不小的开销。
(2)每次赋值都要更新计数器,就要进行加减算法,本来时间就紧张,这样又增加了时间的开销。
(3)最致命,无法处理循环引用的问题。

所以,jvm垃圾回收没有采用这种算法

2、可达性分析算法

从Gc roots开始向下搜索,搜索走过的链路称为引用链,当一个对象到GC ROOTS无通达链路,则对象是不可用的。jvm使用的是这种算法。

(1)什么是GC roots?

GC Roots的对象可以分为两大类:全局对象和执行上下文。

全局对象包括:方法区静态属性引用的对象,方法区常量池引用的对象

执行上下文:
方法栈中栈帧本地变量表引用的对象
本地方法栈中引用的对象
被同步锁持有的对象

  • 如何进行回收?

1、垃圾回收算法

(1)标记-清除算法

首先对堆中的所有对象都扫描一遍,把所有不可达的对象都进行标记,等待时机成熟,一并处理。

这种算法有比较明显的缺点:
标记和清除效率都不高,并且清理后,内存会产生不连续的随便,后续有大的对象需要内存时,不得不分配较大的内存空间,会再次触发GC。

(2)复制算法

首先是对内存分为大小相等的2块,每次使用的时候都使用其中的一块,当一块用完了,进行GC,再将存活的对象复制到另一块,如此反复。

这样缺点也很明显:
首先就是是的原本就吃紧的内存空间减少了一般,同时如果对象太大,复制需要的时间更长,会降低效率。

(3)标记-整理算法

和标记-清除算法相似,但后续步骤不是直接清除,而是先对存活对象进行整理,往一端移动,整齐排列,然后清理掉辩解以外的垃圾对象进行清除。

很好地解决了标记-清除算法的垃圾碎片化问题

(4)分代收集算法

这个算法就是综合以上的算法,将堆分为年轻代和老年代,不同的年代采用不同的算法。

优点明显:在新生代中,对象大小也不会太大,每次垃圾收集都会有大批对象死去,只有少量对象存活,这时候复制算法只需要付出少量存活对象的复制成本就可以完成收集。而老年代对象存活率高,同时对象数量也会较大采用复制算法不合适,因此使用标记清除,标记整理算法。

2、JVM的垃圾回收如何实现

具体看图解
JVM垃圾回收图解

3、什么是minor GC,什么是Major GC(Full GC)

Minor GC,是指发生在新生代中的垃圾回收,这时候,只会回收新生代中产生的垃圾,不会涉及老年代的垃圾回收,回收时间比较快。

Major GC,又称Full GC,是指老年代内存满后,进行的垃圾回收,这时候会连带新生代一起进行垃圾回收,垃圾回收速度比较慢。

4、有哪些垃圾回收器呢?

  • 基础概念

并行(Parallel):并行指的是多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

并发(Concurrent):并行指的是用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU线程上。

吞吐量(Throughput):
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+ 垃圾收集时间)。
JVM垃圾回收图解

  • Serial(搭配Serial Old):单线程,串行收集器最古老最稳定的收集器,可能会产生较长的停顿

实际上Serial收集器并非是“老而无用,食之无味弃之可惜”的鸡肋,到目前为止,它依然是虚拟机运行在Client模式下默认的新生代收集器。它有着优于其他收集器的地方:简单而高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得更高的单线程收集效率。
Serial收集器

  • ParNew(搭配CMS):它是Serial收集器的多线程版本,在单核环境中不比Serial好,但随着CPU线程数量增加,并且可以搭配CMS使用,GC和用户线程可以同时操作,这是很有利的。

ParNew收集器

  • Parallel scavenge (搭配Parallel Old):可以说是ParNew GC收集器的衍生物,区别在于,注重吞吐量,降低垃圾回收时间。

(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))

在这里插入图片描述

  • G1收集器:G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。以关注低延迟为目标、服务器端应用的垃圾回收器。

G1收集器和其他收集器不同:

1)G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。并不会等待内存耗尽再收集垃圾,G1可以根据用户设置的暂停时间目标来自动调整年轻代和总堆大小,暂停目标越短,年轻代空间越小、总空间就越大。

2)G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);

3)G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;

4)G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。
G1收集器

  • CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,从Mark Sweep可以看出CMS收集器基于标记-清除算法。它非常符合在互联网或者B/S系统的服务端上的Java应用,这些应用都非常重视服务的响应速度。

它的运作过程分为4个步骤:

(1)初始标记(CMS initial mark):只是标记一个GC Roots能直接关联到的对象,速度很快。需要“Stop The World”。

(2)并发标记(CMS concurrent mark):进行GC RootsTracing(可达性)的过程。在整个过程中耗时最长。

(3)重新标记(CMS remark):为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这阶段的停顿时间一般比初始标记时间稍长,但远比并发标记时间短。需要“Stop The World”。

(4)并发清除(CMS concurrent sweep):并发清除过程收集器线程和耗时最长的并发标记线程都可以与用户一起工作。

CMS收集器

四、JVM调优

(一) 什么时候进行JVM调优?

当程序遇到性能问题的时候,第一选择应该是优化程序,实在是无法优化时,最后的选择才是JVM调优,因此,鉴于目前学习深度还不足,调优知识后续其他方面知识巩固之后再回来补足。

不得不考虑进行JVM调优的是那些情况呢?

  • Heap内存(老年代)持续上涨达到设置的最大内存值;

  • Full GC 次数频繁;

  • GC 停顿时间过长(超过1秒);

  • 应用出现OutOfMemory 等内存异常;

  • 应用中有使用本地缓存且占用大量内存空间;

  • 系统吞吐量与响应性能不高或下降。

(二) JVM调优目标

吞吐量、延迟、内存占用三者类似CAP,构成了一个不可能三角,只能选择其中两个进行调优,不可三者兼得。

  • 延迟:GC低停顿和GC低频率;

  • 低内存占用;

  • 高吞吐量;

(三) JVM调优的步骤

一般情况下,JVM调优可通过以下步骤进行:

  • 分析系统系统运行情况:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;

  • 确定JVM调优量化目标;

  • 确定JVM调优参数(根据历史JVM参数来调整);

  • 依次确定调优内存、延迟、吞吐量等指标;

  • 对比观察调优前后的差异;

  • 不断的分析和调整,直到找到合适的JVM参数配置;

  • 找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。

在这里插入图片描述

(四) JVM调优基本参数
待补充

(五) JDK调优工具
待补充

标签:垃圾,收集器,基本原理,GC,内存,JVM,图解,加载
来源: https://blog.csdn.net/m0_46702468/article/details/121718987

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有