21JVM概述(1)

JVM

学习目标:

1、了解JVM的发展史;

2、了解JVM运行原理;

3、掌握JVM基本组成;

4、掌握JVM垃圾回收算法;

5、掌握类加载机制;

6、掌握jmm;

1. JVM简介

☁️ JVM 是 Java Virtual Machine 的简称;

☁️ JVM 虚拟机是指通过软件模拟实现的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统;

☁️ 常见的虚拟机有:JVM,VMwave,Virtual Box;

☁️ JVM和其他两个虚拟机的区别:

⚡️ VMwave 与 VirtualBox 是通过软件模拟物理 CPU 的指令集,物理系统中会有很多的寄存器;

⚡️ JVM 则是通过软件模拟 Java 字节码的指令集,JVM 中只是主要保留了 PC 寄存器,其他寄存器都进行裁剪;

🌴 什么是指令集?通俗的理解, 指令集就是CPU能认识的语言,指令集运行于一定的微架构上, 不同的微架构可以支持相同的指令集,比如Intel和AMD的CPU的微架构是不同的,但是同样支持X86指令集,这很容易理解,指令集只是一套指令集合,一套指令规范,具体的实现,仍然依赖于CPU的翻译和执行;

🌴 什么是寄存器? 寄存器的功能是存储二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成;

1.1 JVM发展史

Sun Classic VM

☁️ 在1996年 Java 1.0 版本的时候,Sun 公司发布了一款名为 Sun Classic vm 的 Java 虚拟机,它也是世界上第一款商业Java虚拟机,jdk 1.4 时被完全淘汰;

Exact VM

☁️ 为了解决上一个虚拟机问题,jdk 1.2 时,Sun 提供了此虚拟机;

☁️ Exact 具备现代高性能虚拟机的雏形,包含了以下功能:

⚡️ 热点探测(将热点代码编译为字节码加速程序执行);

当虚拟机发现某个方法或代码块运行的特别频繁时,就会把这些代码认定为“热点代码”(Hot Spot Code),判断一段代码是不是热点代码的这种行为被称为“热点探测”(Hot Spot Detection)

⚡️ 编译器与解析器混合工作模式;

java的编译器先将其编译为class文件,也就是字节码,然后将字节码交由jvm(java虚拟机)解释执行,所以需要用到解析器

HotSpot VM

☁️ HotSpot 历史:

⚡️ 最初由一家名为 “Longview Technologies” 的小公司设计;

⚡️ 1997年,此公司被 Sun 收购;

⚡️ JDK 1.3 时,HotSpot VM成为默认虚拟机;

☁️ 不管是现在仍在广泛使用JDK6,还是使用比较多的JDK8中,默认的虚拟机都是HotSpot;

☁️ 名称中的HotSpot指的就是它的热点代码探测技术;它能通过计数器找到最具编译价值的代码,触发即 时编译(JIT)或栈上替换;通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡;

JRockit

☁️ JRockit 是专注于服务器端应用,目前在HotSpot的基础上,移植JRockit的优秀特性;

☁️ 它可以不太关注程序的启动速度,因此 JRockit 内部不包含解析器实现,全部代码都靠即时编译器编译后执行; 大量的行业基准测试显示,JRockit JVM 是世界上最快的 JVM ;

☁️ 使用 JRockit 产品,客户已经体验到了显著的性能提高(一些超过了 70% )和硬件成本的减少(达 50%);

☁️ JRockit 面向延迟敏感型应用的解决方案 JRockit Real Time 提供以毫秒或微秒级的 JVM 响应时间,适合财务、军事指挥、电信网络的需要;

J9 JVM

☁️ 全称:IBM Technology for Java Virtual Machine,简称 IT4J,内部代号:J9;

☁️ 市场定位于HotSpot接近,服务器端、桌面应用、嵌入式等多用途JVM,广泛用于IBM的各种Java产品;

Taobao JVM

☁️ 由 AliJVM 团队发布,覆盖云计算、金融、物流、电商等众多领域, 需要解决高并发、高可用、分布式的复合问题,有大量的开源产品;

☁️ 基于 OpenJDK 开发了自己的定制版本AlibabaJDK,简称AJDK;是整个阿里JAVA体系的基石;

☁️ 基于 OpenJDK HotSpot JVM 发布的国内第一个优化、深度定制且开源的高性能服务器版 Java 虚拟机,它具有以下特点:

⚡️ 创新的 GCIH(GC invisible heap) 技术实现了 off-heap ,即将生命周期较长的Java对象从heap中移到 heap之外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC的回收评率和提升GC的回收效率的目的;

⚡️ GCIH 中的对象还能够在多个 Java 虚拟机进程中实现共享;

⚡️ 使用 crc32 指令实现 JVM intrinsic 降低 JNI 的调用开销;

⚡️ PMU hardware 的 Java profiling tool 和诊断协助功能;

⚡️ 针对大数据场景的 ZenGC;

☁️ taobao JVM 应用在阿里产品上性能高,硬件严重依赖 intel 的 cpu ,损失了兼容性,但提高了性能,目前已经在淘宝、天猫上线,把 Oracle 官方 JVM 版本全部替换了;

JVM和《Java虚拟机规范》

☁️ 以上的各种 JVM 版本,比如 HotSpot 和 J9 JVM,都可以看做是不同厂商实现 JVM 产品的具体实现,而 它们(JVM)产品的实现必须要符合《Java虚拟机规范》,《Java虚拟机规范》是 Oracle 发布 Java 领域 最重要和最权威的著作,它完整且详细的描述了 JVM 的各个组成部分;

JVM运行流程

☁️ jvm 是 Java 运行的基础,也是实现一次编译到处执行的关键,那么 jvm 是如何执行的呢?

JVM执行流程

☁️ 程序在执行前要先把Java代码转换为字节码(class文件);

☁️ 接着 JVM 需要把字节码通过一定方式的 类加载器(ClassLoader)把文件加载到内存中的运行时数据区(Runtime Data Area)

☁️ 字节码文件并不能直接交给底层操作系统去执行,需要 特定的命令解析器 **执行引擎 (Execution Engine)**将字节码翻译成底层系统指令再交由CPU去执行;

☁️ 翻译过程需要调用其他语言的接口**本地库接口(Native Interface)**来实现整个程序的功能;

JVM 运行时数据区*

☁️ JVM 运行时数据区域也叫内存布局,但需要注意的是它和 Java 内存模型((Java Memory Model,简称 JMM)完全不同,属于完全不同的两个概念;

☁️ JVM 由五大部分组成:

在这里插入图片描述

堆(线程共享)

☁️ 堆的作用:程序中创建的所有对象都在保存在堆中;

☁️ 堆里面分为两个区域:新生代和老生代,新生代放新建的对象,当经过一定 GC 次数之后还存活的对象会放入老生代;新生代还有 3 个区域:一个 Endn + 两个 Survivor(S0/S1) ;

☁️ 垃圾回收的时候会将 Endn 中存活的对象放到一个未使用的 Survivor 中,并把当前的 Endn 和正在使用的 Survivor 清除掉;

Java虚拟机栈(线程私有)

☁️ Java 虚拟机栈的作用:Java 虚拟机栈的生命周期和线程相同,Java 虚拟机栈描述的是 Java 方法的执行;

☁️ 每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数 栈、动态链接、方法出口等信息;

☁️ Java 虚拟机栈中包含了以下 4 部分:

⚡️ 局部变量表:存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用;局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在执行期间不会改变局部变量表大小;简单来说就是存放方法参数和局部变量;

⚡️ 操作栈:每个方法会生成一个先进后出的操作栈;

⚡️ 动态链接:指向运行时常量池的方法引用;

⚡️ 方法返回地址:PC 寄存器的地址;

☁️ 什么是线程私有?

⚡️ 由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器(多核处理器则指的是一个内核)都只会执行一条线程中的指令;因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储;我们就把类似这类区域称之为 “线程私有” 的内存;

本地方法栈(线程私有)

☁️ 本地方法栈和虚拟机栈类似,只不过 Java 虚拟机栈是给 JVM 使用的,而本地方法栈是给本地方法使用的;

☁️ 调用Java方法,就是创建栈帧,放在线程的Java虚拟机中;调用其他语言的函数就是使用本地方法栈;

程序计数器(线程私有)

☁️ 程序计数器的作用:用来记录当前线程执行的行号的;

1、哪些内存区域是线程共享的?哪些是线程线程私有的?

2、每个区域分别保存什么数据?

3、什么是OOM?哪些区域会发生OOM?

除了程序计数器,都有可能发生OOM;

4、哪些区域能发生垃圾回收?

方法区(线程共享)

☁️ 方法区的作用:用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的;也叫永久代(jdk1.7)或元空间(jdk1.8);

⚡️ 常量和静态变量指引用;

☁️ 总结:

在这里插入图片描述

内存布局中的异常问题

Java堆溢出

☁️ Java 堆用于存储对象实例,只要不断的创建对象,并且保证 GC Roots 到对象之间有可达路径来避免来 GC 清除这些对象,那么在对象数量达到最大堆容量后就会产生内存溢出异常;

☁️ 可以设置 JVM 参数 -Xms:设置堆的最小值-Xmx:设置堆最大值;

☁️ 演示内存溢出:

/**
* JVM 参数为:-Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError
* @author 38134
*
*/
public class Test {
     static class OOMObject {
     }
    public static void main(String[] args) {
         List<OOMObject> list =
         new ArrayList<>();
         while(true) {
         list.add(new OOMObject());
         }
     }
}

☁️ 设置参数:

在这里插入图片描述

☁️ 运行下列代码:

在这里插入图片描述

☁️ 内存溢出:

在这里插入图片描述

☁️ 内存溢出和内存泄漏有什么区别?

⚡️ 内存泄漏:线程生命周期太长,导致始终引用一些不使用的数据,这些数据无法回收,导致可用空间越来越少;简单地说就是创建了一块内存却无法将这块内存释放;

⚡️ 内存溢出:所要存储的数据容量大于内存可用容量;

虚拟机栈和本地方法栈溢出

☁️ 由于 HotSpot 虚拟机将虚拟机栈与本地方法栈合二为一,因此对于HotSpot来说,栈容量只需要由-Xss参数来设置;

☁️ 关于虚拟机栈会产生的两种异常

⚡️ 如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出 StackOverflow 异常;

⚡️ 如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OOM异常;

☁️ 观察 StackOverflow 异常(单线程环境下):

/**
* JVM参数为:-Xss128k 
* @author 38134
*
*/
public class Test {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) {
         Test test = new Test();
         try {
         test.stackLeak();
         } catch (Throwable e) {
         System.out.println("Stack Length: "+test.stackLength);
         throw e;
         }
    }
}

☁️ 出现StackOverflowError异常时有错误堆栈可以阅读,比较好找到问题所在。如果使用虚拟机默认参数,栈深度在多数情况下达到1000-2000完全没问题,对于正常的方法调用(包括递归),完全够用;

☁️ 如果是因为多线程导致的内存溢出问题,在不能减少线程数的情况下,只能减少最大堆和减少栈容量的方式来换取更多线程;

/**
* JVM参数为:-Xss2M 
* @author 38134
*
*/
public class Test {
 
    private void dontStop() {
        while(true) {

        }
    }
    public void stackLeakByThread() {
        while(true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop(); 
                }
            });
            thread.start();
        }
    }
 
    public static void main(String[] args) {
        Test test = new Test();
        test.stackLeakByThread();
    }
}

☁️ 以上代码运行需谨慎。先记得保存手头所有工作;

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马孔多镇长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值