jvm相关介绍:

这里写目录标题

  • jvm简介:
      • 类的加载相关:
        • 类的加载:
        • 类加载的生命周期:
        • 类加载器:
        • 类加载机制
      • jvm相关:
        • jdk,jre,JVM的关系:
        • jvm结构:
          • 方法区与堆的区别:
          • 方法区,栈、堆之间的过程:
        • JVM对象分配规则:
        • jvm运行建议过程:
      • 垃圾回收相关:
        • 垃圾回收的几种算法:
        • 复制算法:
        • 标记清除:
        • 标记整理:
        • 垃圾回收器的四种类型:
        • 查看垃圾回收器:
        • java使用的垃圾收集器:
        • 查看使用的垃圾回收器:
        • 垃圾回收器的作用区域:
          • 新生代
            • 串行GC(Serial/Serial Copying)
            • 并行GC(ParNew)
            • 并行回收GC(Parallel/Parallel Scavenge)
          • 老年代
            • 串行GC(Serial Old/Serial MSC)
            • 并行GC(Parallel Old/Parallel MSC)
            • 并发标记清除GC(CMS)
        • 如何选择垃圾回收器:
      • 垃圾回收器总结(重点)
      • G1垃圾回收(重点)
        • G1年轻代回收细节:
        • g1对应的常见参数:
        • G1和CMS(并发标记清除)相比的优势:
        • jdk1.7和jdk1.8的分代区别:

jvm简介:

jvm体系总体分四大块:

  1. 类的加载机制
  2. jvm内存结构
  3. GC算法 垃圾回收
  4. GC分析 命令调优

类的加载相关:

类的加载:

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载的生命周期:

类的生命周期包括这几个部分,加载、连接、初始化、使用和卸载,其中前三部是类的加载的过程,如下图;

  1. 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
  2. 连接,连接又包含三块内容:验证、准备、初始化。1)验证,文件格式、元数据、字节码、符号引用验证;2)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用
  3. 初始化,为类的静态变量赋予正确的初始值
  4. 使用,new出对象程序中使用
  5. 卸载,执行垃圾回收

类加载器:

类加载器的结构图:

  1. 启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库
  2. 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
  3. 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器

类加载机制

  1. 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
  2. 父类委托(双亲委派机制),先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  3. 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

jvm相关:

jdk,jre,JVM的关系:

JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。

jvm结构:

jvm结构图:

  1. Java堆(Heap);
    是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例和数组,几乎所有的对象实例都在这里分配内存。分为新生代和老年代。也是垃圾回收主要的管理区域。
  2. 方法区(Method Area);
    • 与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码,以及编译后的方法实现的二进制形式的机器指令集等数据。
    • 运行时常量池:是方法区的一部分,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放,所以受到方法区内存的限制,当常量量池再申请到内存时会抛出
      OutOfMemoryError异常。
    • 被装载的class的信息存储在Methodarea的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。
    • 指令集是个非常重要概念,因为程序员写的代码其实在jvm虚拟机中是被转成了一条条指令集执行的,看下图

      左侧的foo代码是指令集,可见就是在方法区,程序计数器就不用说了,局部变量区位于虚拟机栈中,右侧最下方的求值栈(也就是操作数栈)我们从动图中明显可以看出存在栈顶这个关键词因此也是位于java虚拟机栈的。
      另外,图中,指令是Java代码经过javac编译后得到的JVM指令,PC寄存器指向下一条该执行的指令地址,局部变量区存储函数运行中产生的局部变量,栈存储计算的中间结果和最后结果。
      上图的执行的源代码是:
      public class Demo {
      public static void foo() {
      int a = 1;
      int b = 2;
      int c = (a + b) * 5;
      }
      }
  3. 程序计数器(Program Counter Register)(PC寄存器);
    • 也叫PC寄存器,是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
    • 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    • 当虚拟机正在执行的方法是一个本地(native)方法的时候,jvm的pc寄存器存储的值是undefined
    • 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
  4. JVM栈(JVM Stacks);
    • 与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。每一个线程有一个。
    • 虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame),栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
    • 局部变量表:存放java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double;和reference,部分的返回结果,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。
    • 动态链接:比如当出现main方法需要调用method1()方法的时候,操作指令就会触动这个动态链接就会找打方法区中对于的method1(),然后把method1()方法压入虚拟机栈中,执行method1栈帧的指令;此外如果指令表示的代码是个常量,这也是个动态链接,也会到方法区中的运行时常量池找到类加载时就专门存放变量的运行时常量池的数据。
    • JAVA虚拟机栈的最小单位可以理解为一个个栈帧,一个方法对应一个栈帧,一个栈帧可以执行很多指令
      jvm相关介绍:_第1张图片
  1. 本地方法栈(Native Method Stacks);
    与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。一般就是指当前方法调用的是c语言写的方法。
方法区与堆的区别:

方法区存放了类的信息,有类的静态变量、final类型变量、field自动信息、方法信息,处理逻辑的指令集,我们仔细想想一个类里面也就这些东西,而堆中存放是对象和数组,咋一看好像方法区跟堆的作用是一样的。
1,这里就关系到我们平时说的对象是类的实例,是不是有点恍然大悟了?这里的对应关系就是 “方法区–类” “堆–对象”,以“人”为例就是,堆里面放的是你这个“实实在在的人,有血有肉的”,而方法区中存放的是描述你的文字信息,如“你的名字,身高,体重,还有你的行为,如吃饭,走路等”。
2,再者我们从另一个角度理解,就是从前我们得知方法区中的类是唯一的,同步的。但是我们在代码中往往同一个类会new几次,也就是有多个实例,既然有多个实例,那么在堆中就会分配多个实例空间内存。

方法区,栈、堆之间的过程:

类加载器加载的类信息放到方法区,–》执行程序后,方法区的方法压入栈–》栈执行压入栈顶的方法–》遇到new对象的情况就在堆中开辟这个类的实例空间。(这里栈是有此对象在堆中的地址的)

JVM对象分配规则:

  1. 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
  2. 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
  3. 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
  4. 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
  5. 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。

jvm运行建议过程:

其中把java文件编译成class文件不属于jvm,那个是用开发工具,比如idea进行编译成的class文件。

垃圾回收相关:

垃圾回收的几种算法:

  1. 引用计数法:
    每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。(所以已经废弃了)
  2. 复制算法:
    发生在年轻代中,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。在复制的时候会比较占用空间内存。
  3. 标记清除:
    发生在老年代中,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。会扫描两次,一次标记,一次清除。会产生不连续的内存碎片。
  4. 标记压缩整理:
    发升在老年代中,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。会扫描两次,一次标记,一次清除后在进行压缩。但是不会产生内存碎片。因为压缩整理就是对清除之后不连续的内存进行排序的一个动作。所以他的效率会比标记清除慢。

垃圾回收一般在堆和方法区内发生。

复制算法:

jvm相关介绍:_第2张图片

标记清除:

jvm相关介绍:_第3张图片

标记整理:

jvm相关介绍:_第4张图片

垃圾回收器的四种类型:

垃圾回收器是对于垃圾回收算法的具体实现。

  1. Serial串行收集器:
    是最古老的收集器,再回收的时候会把用户的所有线程停掉,然后用一个线程去做回收。是单线程的回收。运行在年轻代中。
  2. Parallel并行收集器:
    可以看成是对于串行收集器的一个升级版本。他是使用多个线程去回收,再回收的时候也会把用户的线程停掉。是jdk1.8的默认的垃圾回收器。
  3. CMS(Concurrent Mark Sweep)收集器:
    并发标记清除。用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程。是一种以获取最短回收停顿时间为目标的收集器。
  4. G1:
    在jdk1.7upadte4版本中才正式使用的垃圾回收器,将内存分成不同的区域,然后并行的去进行回收。

查看垃圾回收器:

运行:java -XX:+PrintCommandLineFlags -version 命令可以查看。这里是引用

java使用的垃圾收集器:

jvm相关介绍:_第5张图片

查看使用的垃圾回收器:

这里的HelloGC就是个main方法的类。然后用jinfo -flag命令查看是否使用了这个垃圾回收器,+号表示正在使用,-号表示没有使用。jvm相关介绍:_第6张图片

垃圾回收器的作用区域:

注意:java的新生代和老年代的垃圾回收器是相互影响的,通过设置新生代的GC方式,java就会自动设置老年代的GC方式。相反也是一样的,设置老年代的GC方式之后,新生代也会开启对应的GC方式。

  1. 新生代用:
    串行(Serial/Serial Copying),并行GC(ParNew),并行回收(Parallel/Parallel Scavenge)。这三种垃圾回收机制。
  2. 老年代用:
    串行GC(Serial Old/Serial MSC),并行GC(Parallel Old/Parallel MSC),并发标记清除GC(CMS)
    jvm相关介绍:_第7张图片
新生代
串行GC(Serial/Serial Copying)
  1. 在jvm的参数中配置:-XX:+UseSerialGC。就会开启串行的垃圾回收机制。这时候在Young区会用Serial,早Old区会用SerialOld。都是串行回收。
  2. 开启后会使用:Serial(新生代)+Serial Old(老年代)的垃圾回收器组合。算法:新生代用复制算法,老年代用标记整理。
  3. 说白了就是新生代和老年代都是用的串行回收。

在这里插入图片描述

并行GC(ParNew)
  1. 开启方式和串行一样,只不过参数换成了:-XX:+UseParNewGC。
  2. 开启后会使用:ParNew(新生代)+CMS/Serial Old(老年代)的垃圾回收器组合。
    在jdk1.8之后,首先会使用ParNew(新生代)+CMS(并发标记清除)(老年代)的方式进行回收,但是一旦老年代的CMS失败了,就会触发:ParNew(新生代)+Serial Old(老年代)这个组合(相当于一个保底机制)。
    失败原因:见下面的老年代的CMS介绍。
  3. 使用的算法:新生代用复制算法,老年代用标记清除。
  4. 说白了就是新生代用并行回收,老年代还是用串行回收。所以这种组合已经不推荐了。
并行回收GC(Parallel/Parallel Scavenge)
  1. 在jdk1.6之前。新生代Parallel匹配的是老年代Serial Old方式。在1.8jdk之后(包括1.8)才使用新生代Parallel+老年代Parallel Old的方式。
  2. 1.8jdk默认的垃圾回收器。
  3. 参数换成了:-XX:+UseParallelGC或者-XX:+UseParallelOldGC。推荐设置-XX:+UseParallelOldGC参数,因为他会自动设置年轻代GC方式。防止使用的是1.6版本之前的jdk。
  4. 说白了就是在新生代和老年代都是用的并行回收的方式。
  5. Parallel和ParNew区别:Parallel有一种自适应调节策略。Parallel会收集性能监控的数据,动态的调整参数以便提供最大的吞吐量和最小的停顿时间。
  6. 算法:新生代用复制算法,老年代用标记整理。
  7. 可用参数“-XX:ParallelGCThreads=数字” 表示启动了多少个gc线程。
    cpu核数>8 配成5/8
    cpu核数<8 配置成cpu的核数
老年代
串行GC(Serial Old/Serial MSC)

参考年轻代的配套GC方式。老年代和年轻带的GC方式是配套的。

并行GC(Parallel Old/Parallel MSC)

参考年轻代的配套GC方式。老年代和年轻带的GC方式是配套的。

并发标记清除GC(CMS)
  1. 参考年轻代的配套GC方式。老年代和年轻带的GC方式是配套的。
  2. 分为4步
    • 初始标记:会停止用户线程。
    • 并发标记:不会停止用户线程。并行处理。
    • 重新标记:会停止用户线程。对于初始标记对象的二次确认。
    • 并发清除:不会停止用户线程。并行处理。
  3. 所以在总体上是和用户线程一起并行运行的。所以现在一般都是用这种(在G1没有出来之前)。他是底停顿的,但是由于并发处理,所以会增加对于堆内存的占用,故而,必须要在堆内存用完之前进行cms回收,不然就会导致失败。进而使用串行老年代收集,会造成系统的停顿。

如何选择垃圾回收器:

jvm相关介绍:_第8张图片

垃圾回收器总结(重点)

jvm相关介绍:_第9张图片
上面的垃圾回收算法的特点(不包括G1):

  1. 年轻代和老年代是各自独立,且连续的内存块
  2. 年轻代收集器使用的是复制算法
  3. 老年代收集必须扫描整个老年代区域
  4. 都是尽可能少且快速的执行GC为设计原则

G1垃圾回收(重点)

  1. 开启参数:-XX:+UseG1GC。唯一一个横跨年轻代和老年代的垃圾回收机制。在jdk1.7的upate4版本中才有的。在jdk1.9的时候才是默认的垃圾回收器。
  2. 类似于CMS一样,可以和用户线程并行的执行回收线程。设计目标就是为了取代CMS(并行标记清除)。
  3. 在宏观上不在区分老年代和新生代,把整个内存分为一块一块的进行回收操作(在具体的分割的区域还有年轻代和老年代的区分的,也就是说针对不同的内存区域,还是会分不同的GC处理)。旨在逻辑上区分年轻代和老年代。不在有物理隔离了。每个区域大小从1M-32M不等,而且随着GC的执行,每个区域也在变化,比如一个区域一开始年轻代,下次的时候可能就变成老年代了。
  4. 尽可能的利用了多CPU,多核的硬件优势,尽量缩短了STW(程序暂停)。在整体上采用了标记-整理的算法(所以不会产生内存碎片),局部采用了复制算法。在停顿上添加了预测机制,用户可以指定期望时间。
  5. 启动的时候可以添加参数:-XX:G1HeapRegionSize=大小。指定每个分区的大小(1M-32M不等,不过必须是2的幂),默认将堆分成2048个分区。既最大支持内存:32*2048=64G的内存。
  6. g1和其他的垃圾回收器的区别:
    jvm相关介绍:_第10张图片
    jvm相关介绍:_第11张图片

G1年轻代回收细节:

jvm相关介绍:_第12张图片
回收步骤也是四步:

  1. 初始标记:
    只标记GC Roots能关联的对象。
  2. 并发标记:
    进行GC Roots Tracing的过程。
  3. 最终标记:
    修正并发标记期间,因程序运行导致标记发生变化的对象。
  4. 筛选回收:
    根据时间进行价值最大化回收。

其中在初始标记的时候也会停止用户的线程。

g1对应的常见参数:

jvm相关介绍:_第13张图片

G1和CMS(并发标记清除)相比的优势:

  1. G1不会产生内存碎片。
  2. 可以控制停顿,G1把整个内存分成一个个内存区域。可以根据设置的停顿时间去垃圾回收最多的区域。

jdk1.7和jdk1.8的分代区别:

在jdk1.7以前,是分为,年轻代(伊甸园区,幸存者1,2区),老年代,永久代。
在1.8jdk(包含1.8)以后,就没有永久代这个概念了。而是元空间。其他的一样。
最大的区别就是:永久代使用的是jvm的堆内存,而元空间使用的是本机物理内存不再是虚拟机内存了。所以类的元数据放在了本地内存中了。就不再是MaxPermSize控制的了,而是由系统实际空间来控制。

你可能感兴趣的:(java基础,jvm)