jvm是什么_JVM到底是个什么东东?

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。

1. JVM的位置

JVM一般处于操作系统的顶层,应用程序需要首先经过JVM编译才能被操作系统理解,进而被计算机硬件理解

6e209fd24ceda3188e30dafb12888ada.png

2. JVM的体系结构

af1574a5b11744dbebeddc7107a9f59c.png

3. 类加载器

97a41c81d0afab5132530eff434628a6.png

测试:

 1public class Car {
2    public static void main(String[] args) {
3        Car car1 = new Car();
4        Car car2 = new Car();
5        Car car3 = new Car();
6
7        System.out.println(car1.hashCode());  //356573597  new 的对象不一样
8        System.out.println(car2.hashCode());  //1735600054
9        System.out.println(car3.hashCode());  //21685669
10
11        Class extends Car> aClass1 = car1.getClass();
12        Class extends Car> aClass2 = car2.getClass();
13        Class extends Car> aClass3 = car3.getClass();
14
15        System.out.println(aClass1.hashCode());  //1956725890  字节码都一样
16        System.out.println(aClass2.hashCode());  //1956725890
17        System.out.println(aClass3.hashCode());  //1956725890
18
19        ClassLoader classLoader = aClass1.getClassLoader();
20        ClassLoader classLoader = aClass1.getClassLoader();
21        System.out.println(classLoader);//AppClassLoader  
22        System.out.println(classLoader.getParent());//ExtClassLoader  /jre/lib/ext
23        System.out.println(classLoader.getParent().getParent()); //null  rt.jar
24    }
25}

4. 双亲委派机制

当某个类加载器需要加载某个 .class文件时,它首先把 这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
9e592f254898a4a57c823f9b99708a18.png
 1package java.lang;  //与rt中的String类同包
2public class String {
3    //双亲委派机制
4    //App->EXC->BOOT(最终执行)
5    //寻找顺序:BOOT加载器-> EXC加载器-> App加载器
6
7    public String toString() {
8        return "hello";
9    }
10
11    public static void main(String[] args) {
12        System.out.println(new String().toString());
13    }
14}

执行结果:

42db5412cf15ca502c39ef5a7811932c.png

由于rt.jar下的java.lang包下String类没有main方法,所以报错,这是因为程序会首先在rt.jar包下找String类

5. 沙箱安全机制

Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱 主要限制系统资源访问,那系统资源包括什么?—— CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

6. Native

 1public class Test {
2    public static void main(String[] args) {
3        new Thread(()->{},"my Thread name").start();
4    }
5
6    //凡是带有native关键字,说明java的作用范围达不到了
7    //回去调c语言的库了
8    //会调用本地方法栈
9    //调用本地方法接口 JNI: 扩展java的使用 融合不同的编程语言
10    //java诞生的时候 c,c++横行,想要立足就必须调用c,c++
11    //它在内存中专门标记了一块区域:native method stack
12
13    private native void start0();  //没有报错
14
15    //调用其他接口的其他的方法:socket,https,webservice ...
16
17}

7. PC寄存器

每个线程启动的时候,都会创建一个PC(Program Counter,程序计数器)寄存器。PC寄存器里保存有当前正在执行的JVM指令的地址。每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。保存下一条将要执行的指令地址的寄存器是 :PC寄存器。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。

8. 方法区

377c74ad9b8f3888da93dbb7fdc30576.png

方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数、接口代码也在此定义,简单地说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关,例如( static、final、C lass、 常量池)。举例:“qinjiang” 放入了常量池

42173b07a12b52f282d38067a3630fcf.png

9. 栈

每个栈桢中都存储着:
  • 局部变量表

  • 操作数栈(或表达式栈)

  • 动态链接(或指向运行时常量池的方法引用)

  • 方法返回地址(方法正常退出或者异常退出的定义)

  • 一些附加信息

 1//下面一个例子和图示来详细解释对象内存布局
2public class Customer {
3    int id = 1001;
4    String name;
5    Account account;
6    {
7        name = "匿名客户";
8    }
9    public Customer(){
10        account = new Account();
11    }
12}
13class Account{
14}
15
16public class CustomerTest {
17    public static void main(String[] args) {
18        Customer cust = new Customer();
19    }
20}

5678551f3ccc219bd42b05a55110f3ad.png

对象访问方式有两种:
  1. 句柄访问

44f6b5bbbd30c0321d7ce90d39405208.png

  1. 直接指针(HotSpot采用)

afd8936f04c230a8e5402c8da9d27492.png

10. 三种JVM

  • Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)

  • BEA JRocket

  • IBM J9 VM

11. 堆

一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。
  • 堆内存的大小是可以调节的。

在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。堆空间细分为:
  • Java 8 及以后堆内存逻辑上分为三部分:新生代、老年代、元空间

  • Young Generation Space 新生代 Young/New

    • 又被划分为Eden区和Survivor区

  • Tenure Generation Space 老年代 Old/Tenure

  • Meta Space 元空间 Meta

    1605020781998

    4185669704463982a8cd34169d8b4baa.png

    a51d299f083d0887f545be3b3749eb60.png

GC主要在伊甸园区和养老区, 假设内存满了,OOM,堆内存不够,就会报以下错误
1Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
JDK8以后永久区改了个名子( 元空间)

12. 永久区

jdk1.6以前:永久代,常量池放在方法区jdk1.7 : 永久代,但是慢慢退化了,去永久代,常量池在堆中jdk1.8 : 无永久代,常量池在元数据

13. 堆内存调优

  • Java堆区用于存储Java对象实例,那么堆的带下在JVM启动时就已经设定好了,可以通过选项“-Xmx” 和“-Xms”来进行设置。

  • “-Xms”用于表示堆区的初始内存,等价于-XX:InitialHeapSize

  • “-Xmx”用于表示堆区的最大内存,等价于-XX:MaxHeapSize

  • 一旦堆区中的内存大小超过“-Xmx”所指定的最大内存时,将会抛出OutOfMemoryError异常。

  • 通常会将-Xms和-Xmx设置相同的值,其目的是为了能够在Java垃圾回收机制清理完堆区后不需要重新分隔计算堆区大小,从而提高性能。

  • 默认情况下,初始内存大小:物理电脑内存大小/64,最大内存大小:物理电脑内存大小/4

 1public class Hello {
2    public static void main(String[] args) {
3        Long num=1024l*1024;
4        Long maxMemory = Runtime.getRuntime().maxMemory();
5        Long total = Runtime.getRuntime().totalMemory();
6        System.out.println("max: "+maxMemory/num);
7        System.out.println("total: "+total/num);
8
9        //-Xms1024m -Xmx1024m -XX:+PrintGCDetails
10          //报OOM
11              //尝试扩大堆内存
12              //尝试分析内存,看哪里出问题
13
14    }
15}

将最大堆内存设置为1024m,总堆内存设置为1024m

1-Xms1024m -Xmx1024m -XX:+PrintGCDetails

88e511ecacff4f691195ea908e7276d3.png

如上图,元空间逻辑上存在,物理上不存在

1(30566+699392)/1024=981.5    #已经没有元空间的位置,占满了
在一个项目中突然出现了OOM故障,那么该如何排除?
  • 能够看到第几行出错,内存快照分析工具,MAT,Jprofile

  • Debug,一行行分析代码

MAT,Jprofile作用:
  • 分析Dump内存文件,快速定位内存泄露

  • 获得堆中的数据

  • 获得大的对象

14. 安装Jprofiler

  1. IDEA安装jprofiler插件

e178fdab276497a3b73f05daf716e94a.png

  1. 安装jprofiler软件(本地在E:\servers\JProfiler11_x64_jb51)

直接无脑下一步,安装在" C:\Program Files\jprofiler11"
  1. 在IDEA中将插件与外部jprofiler安装位置建立联系

6290811dd71a10ddb2e68948280b3b60.png

1605059156136
  1. 测试

 1//-Xms 设置初始化内存大小  默认1/64
2//-Xmx 设置最大分配内存    默认1/4
3//-XX:+PrintGCDetails    打印GC垃圾回收
4//-XX:+HeapDumpOnOutOfMemoryError    oom Dump
5//-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError
6public class Demo1 {
7    byte[] arr=new byte[1*1024*1024];
8
9    public static void main(String[] args) {
10        ArrayList list=new ArrayList<>();11        int count=0;1213        try {14            while(true){15                list.add(new Demo1());16                count+=1;17            }18        } catch (Exception e) {19            System.out.println("count: "+count);20            e.printStackTrace();21        }22    }232425}

调试堆内存为1m进行测试

a4521acc410355de320cd7812056697a.png

测试结果:

513d05ad5cf3cb8bdd35a8b999f2cd92.png

直接双击点开,可以直接定位到出错的代码:

68d1b8c7196f97c1ecb1e8252cb49875.png

可以看到大变量:

8216eff1a42ba7a368da2c3196e5f556.png

1605060221678

具体见:https://www.cnblogs.com/andy-zhou/p/5327288.html

15. GC

常用算法: 引用计数(Reference Counting):比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。 标记-清除(Mark-Sweep):

82bf3b11accafc772df58b29d89689c7.png

此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。 复制(Copying):

5af94afb9b6514501cef56736923ae3b.png

此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。 标记-整理(Mark-Compact):

dddb548a07c056c830bdb8990d89933e.png

此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

16. JMM

什么是JMM?

JMM即为JAVA 内存模型(java memory model)。因为在不同的硬件生产商和不同的操作系统下,内存的访问逻辑有一定的差异,结果就是当你的代码在某个系统环境下运行良好,并且线程安全,但是换了个系统就出现各种问题。Java内存模型,就是为了屏蔽系统和硬件的差异,让一套代码在不同平台下能到达相同的访问结果。JMM从java 5开始的JSR-133发布后,已经成熟和完善起来。

JVM内存划分

JMM规定了内存主要划分为 主内存和工作内存两种。此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,如果非要对应起来, 主内存对应的是Java堆中的对象实例部分工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。

JVM内存交互操作

  • - 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操作之前,必须把此变量同步回主内存

JMM对这八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值