JVM初探

JVM初探

一、了解JVM

1.JVM的概述

JVM是Java Virtual Machine(java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,用来执行java字节码(二进制的形式)

JDK(Java Development Kit),它是实际上存在的,它包含JRE+编译、运行等开发工具.

JRE(Java Runtime Environment),它用于提供运行时环境。它是JVM的实现。它是实际存在的。它包含一组系统类库和JVM。

Java平台可分为两部分,即Java虚拟机(Java virtual machine,JVM)和Java API类库

引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码,就可以在多种平台上不加修改地运行。

2.JVM的作用

JVM是用来解析和运行Java程序的

Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

3.JVM的分类

  • Sun 公司 Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
  • BEA JRockit
  • IBMJ9VM

4.JVM的位置

jvm是运行在操作系统之上的,与硬件没有任何关系。

image-20211018185115662

5. JVM的体系结构

image-20211018192759080

二、类加载器

image-20211018211822598

类加载器通常由JVM提供,

作用:类的class文件读入到内存,并为之创建一个java.lang.Class对象

类是模板(抽象的),对象是具体的。

分类:

  1. 虚拟机自带的加载器
  2. 启动类(根)加载器(BootstrapClassLoader):主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader
  3. 扩展类加载器 (ExtClassLoader) : 主要负责加载jre/lib/ext目录下的一些扩展的jar
  4. 应用程序加载器 (AppClassLoader) : 主要负责加载应用程序的主函数类

三、双亲委派机制

介绍:

  1. 类加载器收到类加载的请求!

  2. 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器

  3. 启动类加载器检查是否能加载这个类,能加载就结束,使用当前的加载器,否则则,抛出异常,通知子类加载器进行加载

  4. 重复步骤3

  5. Class Not found

    null : java调用不到,c,c++

    Java = C++ --: 去掉繁琐的步骤例如指针,内存管理交给JVM

image-20211018210950270

**双亲委派机制作用:**防止替换系统级别的类

例如如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

四、沙箱安全机制

1.概述

Java安全模型的核心就是Java沙箱(sandbox)。什么是沙箱?沙箱是一个限制程序运行的环境

沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离防止对本地系统造成破坏

五、Native

例:

public class Test {

    public static void main(String[] args) {

        new Thread().start();
    }
}
 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库!

会进入本地方法栈,调用本地方法,本地接口(JNI:java native interface)

JNI作用:扩展Java的使用,融合不同的编程语言为Java使用! 最初:c,c++

**为什么要用Native:**Java诞生的时候c,c++横行,想要立足,必须要调用c,c++的程序

它在内存中专门开辟一块标记区域:Native Method Stack(本地方法栈) , 登记 native方法,在最终执行的时候,通过JNI加载本地方法库中的方法

**应用:**java程序驱动打印机,管理系统,在企业级应用中比较少见!

调用其他接口:Socket…WebService~…http

image-20211018215749727

六、PC寄存器

Pc寄存器是什么?

程序计数器:Program Counter Register

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要指向的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

在这里插入图片描述

PC寄存器中存储的就是操作数栈的指令地址,交由java的执行引擎执行

img

七、方法区(Method Area)

方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单的说,所有定义的方法的信息都保存在该方法中,此区域属于共享区间。

静态常量,常量,类信息(构造方法,接口定义),运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。

static, final, Class , 常量池

public class Test {
    
    private int a = 6;
    private String name = "qinjiang";
    

    public static void main(String[] args) {

        Test test1 = new Test();
    }
}

image-20211018224919043

八、栈(Stack)

栈特点:先进后出

队列:先进先出(FIFO:FIrst Input First Output)

栈:栈内存,主管程序的运行,生命周期和线程同步。

线程结束,栈内存释放,对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就over.

栈:8大基本类型 + 对象引用 + 实例的方法

栈运行原理:栈帧

1.一个对象在内存中实例化的过程(粗略):

public class People{
    
    String name; // 定义一个成员变量 name
    int age; // 成员变量 age
 
    void showInfo(){
        System.out.println("人的姓名:"+name);
        System.out.println("人的年龄:"+age);
    }
    
    public static void main(String[] args) {
        String name; // 定义一个局部变量 name
    	int age;     // 局部变量 age
        
        People people = new People() ; //实例化对象people
        people.name = "张三" ;       //赋值
        people.age = 18;             //赋值
        people.showInfo();              //调用方法showInfo
    }
}
  1. 在程序进行过程中,首先类中的成员变量和方法会进入到方法区

    image-20211018235549474

  2. 程序执行到 main() 方法时,main()函数方法体会进入Java栈,定义了一个用于指向 People 实例的变量 people。然后将成员变量和成员方法放在 new 实例中都是取成员变量和成员方法的地址值 如图:

image-20211018235549474

  1. 接下来对 People对象进行赋值,people.name = “张三” ; people.age = 18;

    先在栈区找到 People,然后根据地址值找到 new Person() 进行赋值操作

img-Mk1M8tsH-1634820185304

4.当程序走到 people.showInfo()方法时,先到栈区找到 peo这个引用变量,然后根据该地址值在堆内存中找到 new People() 进行方法调用。

在方法体void showInfo()被调用完成后,就会立刻马上从栈内弹出(出站 )

最后,在main()函数完成后,main()函数也会出栈 如图:
image-20211020210147277

九、堆(Heap)

一个JVM只有一个堆内存,堆内存大小可以调节

类加载器读取了类文件后,一般会把什么东西放到堆中?

类,方法,常量,变量,保存我们所有引用类型的真实对象;

堆内存分为三个区域:

  • 新生区(伊甸园区) Young/New
  • 养老区 old
  • 永久区 Perm
    img-DSTp6nAJ-1656856835274

GC垃圾回收,主要在伊甸园和养老区

假设内存满了,OOM,堆内存不够!,java.lang.OutOfMemoryError:lava heap space

在JDK 8 以后,永久区改了名字(原空间);

1.新生区

类:诞生和成长的地方,甚至死亡;

  • 伊甸园:所有的对象都是在伊甸园中new出来。
  • 幸存者区(0,1)
2.永久区

永久区常驻内存的。用来存放JDK自身携带的class对象,interface元数据,存储的是java运行时的一些环境或者类信息,永久区不存在垃圾回收,关闭虚拟机就会释放这个区域的内存。

一个启动类,加载了大量的第三方jar包,Tomcat部署了太多应用,大量动态生成反射类,不断的被加载,直到内存满,就会出现OOM.

  • jdk1.6 之前:永久代

  • jdk1.7 : 永久代,但是慢慢的退化了,去永久代,常量池在堆中

  • jdk1.8 之后:无永久代,常量池在元空间。
    image-20211019161723084

    元空间逻辑上存在堆中,物理上不存在堆中

    public class Car {
    
        public static void main(String[] args) {
    
            //返回虚拟机视图使用的最大内存
            long max = Runtime.getRuntime().maxMemory();
    
            //返回JVM的初始化总内存
            long total = Runtime.getRuntime().totalMemory();
    
            System.out.println("max = "+max+"字节\t"+(max/(double)1024/1024)+"MB");
            System.out.println("total = "+total+"字节\t"+(total/(double)1024/1024)+"MB");
        }
    }
    

image-20211019155120915

由图可知,默认情况下分配的总内存是电脑内存的1/4,初始化内存为电脑的1/64。

接下来,调整JVM可使用的内存大小

image-20211019160726152

接下来查看控制台输出
image-20211019163246757

重新写一个测试类,测试OOM情况

public class Test {

    public static void main(String[] args) {

        String str = null;

        while(true){
            str += str + new Random().nextInt(88888888)+new Random().nextInt(99999999);
        }
    }
}

调整JVM可使用的内存大小为8MB
image-20211019164309507

测试运行,查看运行结果
image-20211019165206063

排查OOM错误:研究为什么会出错

  • 能够看到第几行代码出错:内存快照分析工具,MAT(eclipse), Jprofiler

    MAT(eclipse), Jprofiler作用:

    • 分析Dump内存文件,快速定位内存泄漏
    • 获得堆中的数据
    • 获得大的对象
  • debug : 一行行地分析代码!

安装Jprofiler的安装和使用:

  • IDEA File->Setting 安装插件jprofiler,安装好后重启IDEA

image-20211019170856725

  • 前往jprofiler官网 https://www.ej-technologies.com/products/jprofiler/overview.html下载jprofiler9.2版本
    image-20211020144815870

  • jprofiler客户端安装路径不能有中文和空格,安装好后无脑下一步,直到输入许可证信息,许可证秘钥
    L-Larry_Lau@163.com#36573-fdkscp15axjj6#25257
    image-20211020145203475

    • 打开IDEA 配置JProfiler,找到安装路径下的jprofiler/bin/jprofiler.exe

image-20211020162546640

  • 配置 Car calss ,测试OOM问题所在 -Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
    image-20211020163108864image-20211020163551972
  • 打开生成的dump文件,查看进程详细信息。

image-20211020163833104

十、GC

1.GC的概述

GC(garbage collec) 垃圾回收

GC是对JVM中的内存进行标记和回收,Sun公司的JDK用的虚拟机都是HotSpot
对象化的实例是放在heap堆内存中的,这里讲的分代收集也是指对堆内存的回收

JVM在进行GC的时候,大部分的时候回收都是新生代。

GC 两种类:轻GC(普通GC), 重GC(全局GC)

1.GC 题目

  • JVM的内存模型和分区~详细到每个分区放什么?
  • 堆里面的分区有哪些?fdem,form,to old ,老年区,元空间
  • gc算法有哪些?标记去除法,标记压缩,复制算法,引用计数器,怎么用的
  • 轻GC和重GC分别在什么时候发生?

2.GC算法

  • 复制算法

    如果一个对象经历类15此GC都没有死,进入到养老区。

    可以通过 -XX:MaxTenuringThreshold=16 来重新设定进入老年区的时间
    image-20211020181823393image-20211020183254954

    • 复制算法的优点:没有内存的碎片
    • 复制算法的坏处:浪费了内存空间:多了一半空间永远是空to,万一对象100%存活(极端情况)

    ​ 复制算法最佳使用场景:对象存货度较低的情况。

  • 标记清除算法
    image-20211020184551029

    • 优点:不需要额外的空间。
    • 缺点:两次扫描,严重浪费时间,会产生内存碎片。
  • 标记压缩算法

    对标记清除算法的优化。

    image-20211020185159918

    • 缺点:多了移动成本
  • 标记清除压缩

    先标记清除几次,再压缩。

  • 总结

    内存效率:复制算法>标记清除算法> 标记压缩算法(时间复杂度)

    内存整齐度:复制算法 = 标记压缩算法>标记清除算法

    内存利用率:标记压缩算法 = 标记清除算法>复制算法

​ 难道没有最优算法吗?

​ 答:没有,没有最好的算法,只有最合适的算法-------->GC:分代收集算法

​ 年轻代: 存活率低 ---->复制算法

​ 老年代:区域大:存活率 ----->标记清除(内存碎片不是太多)+标记压缩混合实现

十一、JMM

1.JMM概述

JMM的全称是 Java Memory Model(Java内存模型)

JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的,这也是Java解决多线程并行机制的环境下,定义出的一种规则,意在保证多个线程间可以有效地、正确地协同工作。

2.JMM作用

作用:缓存一致性协议,用于定义数据读写的规则!

JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)

image-20211020193041717

解决共享对象可见性这个问题:voliate

JMM的八种交互操作(每个操作都为原子操作)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
    store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
  • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对八种操作的规则

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

十三、总结与补充

1.问题

  • 请你谈谈对JVM的理解,java8虚拟机和之前的变化更新
  • 什么是OOM,什么是栈溢出StackOverFlowError?怎么分析?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取,怎么分析Dump文件?
  • 谈谈JVM中,你对类加载器的认识

推荐:多学习,多了解面试题,书籍《深入了解JVM》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值