JVM虚拟机调优实战(1)概念篇

JVM虚拟机

1.jvm是用什么语言编写的

c和c++;


2.JVM是Sun HostSport VM 和BEA JRockit VM 合并版

这就解释了为什么再jdk1.8将java虚拟机永久代改为了元空间,甲骨文收购了Sun HostSport VM 和BEA JRockit VM 两家公司,再甲骨文公司内部这两款虚拟机斗争过程当中,BEA JRockit VM胜利,并且java之父远走甲骨文。

在此解释一下元空间,永久代,方法区的概念:
《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 同时大多数用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集扩展至方法区,或者说使用永久代(元空间)来实现方法区。因此,我们得到了结论,永久代(元空间)是HotSpot的概念,方法区是Java虚拟机规范中的定义,是一种规范,而永久代(方法区)是一种实现,一个是标准一个是实现。


3.JVM三个主要子系统及作用步骤

  1. 类加载器子系统
  2. 运行时数据区(内存结构)
  3. 执行引擎
    在这里插入图片描述

作用步骤:第一步将XXX.class文件装载到内存当中去,第二部在将一些对象信息装载到运行时数据去,第三部到执行引擎执行java的程序,这里也就说会产生垃圾收集器收集。


4.JVM简述运行时数据区各部分作用

方法区:线程共享的,是Java 虚拟机规范中定义方法区是堆的一个逻辑部分(内部包含已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码)

堆:线程共享的,是用来存放对象的内存空间,几乎所有的对象都存储在堆中。

java栈:线程私有的,描述 Java 方法运行过程的内存模型。内部包含栈帧,栈帧内部包含(局部变量表,操作数栈,动态链接,方法出口信息)

本地方法栈:线程私有的,为 JVM 运行 Native 方法准备的空间,由于很多 Native 方法都是用 C 语言实现的,所以它通常又叫 C 栈。它与 Java 虚拟机栈实现的功能类似,只不过本地方法栈是描述本地方法运行过程的内存模型。在早期java为了适配C语言,c语言再早期比java流行,所以要适配C和C++语言,但现在用的较少。

程序计数器:线程私有的,程序计数器是一块较小的内存空间,是当前线程正在执行的那条字节码指令的地址。若当前线程正在执行的是一个本地方法,那么此时程序计数器为Undefined。作用是(1)字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。(2)在多线程情况下,程序计数器记录的是当前线程执行的位置,从而当线程切换回来时,就知道上次线程执行到哪了。

4.JVM简述运行时数据区的Java栈

栈模型及其作用:

在这里插入图片描述

注意:JVM会给一个线程的每一方法分配一个栈帧


举例(通过例子了解整个java栈的运行过程):

(1)java示例代码:


public class Math  {
    
    


    public int math(){
    
    
        int a =1;
        int b=2;
        int c=a+b;
        return c;
    }

    public static void main(String[] args) {
    
    
        Math math = new Math();
        System.out.println(math.math());
    }
}

(2)进行编译javac命令,得到字节码文件
(3)使用javap命令对.class文件进行反汇编,这样我们才能读懂jvm堆内存执行的命令

javap -c Math.class > Math.txt

这样就得到反汇编后得到的文件

Compiled from "Math.java"
public class Math {
    
    
  public Math();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int math();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_3
       8: iload_3
       9: ireturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Math
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1
      12: invokevirtual #5                  // Method math:()I
      15: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      18: return
}

(4)读懂这些指令文件,这里要使用jvm指令集
在这里插入图片描述
(5)解析形成的指令文件Math.txt

  1. 首先会发现JVM先走了math()方法,math()第一个指令为0: iconst_1(指令可以对照上边指令集),0: iconst_1意思是常量值会先压入操作数栈的栈顶。如图:在这里插入图片描述
  2. 1: istore_1指令是指将操作数栈的1出栈,然后1这个值进局部变量表并赋值给变量a。如图:在这里插入图片描述
    3.2: iconst_2 3: istore_2做步骤一和步骤二相同原理、如图:在这里插入图片描述

4.4: iload_1,该指令意思是将例如a=1,也就是第一个值,从局部变量表中拿出来,再压入到操作数栈(5: iload_2同理):在这里插入图片描述
在这里插入图片描述

5.6: iadd将操作数栈栈顶的两个数相加,再将结果压入到栈顶在这里插入图片描述
6.7: istore_3 8: iload_3 9: ireturn就是将3这个值出栈再放到变量表并复制给c,并将结果给返回


总结栈帧各部分:
   局部变量表:存放方法的一些局部变量。
   操作数栈:存放临时的一些用于操作的数;
   动态链路:例如main()方法中存放着这局部变量Math,而Math引用着元空间的示例。而这个引用就是动态链路。如图:
在这里插入图片描述

   方法出口:记录下一个方法的执行位置,和程序计数器的数字;

5.JVM简述运行时数据区的方法区

解释:线程共享的,存放的是类的的所有字段和方法字节码,简单来说,所有定义方法的信息都存到该区域(静态变量+常量+类信息(构造方法+接口定义)+运行时常量池),别名Non-Heap(非堆)

6.JVM简述运行时数据区的堆

堆:在这里插入图片描述
堆垃圾回收:垃圾回收首先会先从新生代的Eden区回收(这里是YGC),回收没有引用的对象,如果垃圾回收完Eden区,存活的对象会先放到Surivior space 存活区的From区,再一次垃圾回收(YGC)的时候,会回收Eden和From区没有引用的对象,如果垃圾回收完Eden区和From区,存活的对象会先放到Surivior space 存活区的To区,这次垃圾回收完毕后,原先的From区转化为To区,To区转换为From区,再下一次回收的时候,就会从原来的To区(其实就是现在转换好的From区)和Eden区再次回收,这样重复15次,如果对象还在存活,那么就放入老年代,如果老年代也超出了预先再配置文件中设置的阈值,那么就会好触发Full GC,Full GC的触发就会影响整个服务,导致服务的瘫痪,所以调优一般调优的是Full GC。

7.JVM类加载子系统

1.类加载过程
在这里插入图片描述

类加载:类加载器将class文件加载到虚拟机的内存
   加载:在硬盘上查找并通过IO读入字节码文件
   连接:执行校验、准备、解析(可选)步骤
   校验:校验字节码文件的正确性
   准备:给类的静态变量分配内存,并赋予默认值
   解析:类装载器装入类所引用的其他所有类
   初始化:对类的静态变量初始化为指定的值,执行静态代码块

2.类加载器种类

   启动类加载器:负责加载JRE的核心类库,如jre目标下的rt.jar,charsets.jar等
   扩展类加载器:负责加载JRE扩展目录ext中JAR类包
   系统类加载器:负责加载ClassPath路径下的类包(加载自己的程序)
   用户自定义加载器:负责加载用户自定义路径下的类包

3.类加载机制(两种)
全盘负责委托机制:当一个ClassLoader加载一个类时,除非显示的使用另一个ClassLoader,该类所依赖和引用的类也由这个ClassLoader载入(不常用),实例如图:
在这里插入图片描述

双亲委派机制:指先委托父类加载器寻找目标类,在找不到的情况下在自己的路径中查找并载入目标类
在这里插入图片描述
4.双亲委派模式优势

沙箱安全机制:自己写的String.class类不会被加载,这样便可以防止核心API库被随意篡改。首先String类属于引导类记载器加载的类,如果自己写的String.class被加载的话,那么这个类会被随意的修改,暴露安全隐患。双亲委派机制就会避免这种情况的发生,因为自己写的String类不会被加载,而被引导类加载器加载,这样就避免了自己写的String被加载。

避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再 加载一次

5.JVM加载jar包是否会将包里的所有类全部加载进内存?

不会,JVM对class文件是按需加载(运行期间动态加载),非一次性加载。

猜你喜欢

转载自blog.csdn.net/qq_41133245/article/details/108938586