JVM运行时数据区分析

2021-10-11  本文已影响0人  双囍_小赵

这篇来分析一下,JVM的构成原理内容。

JVM是一种规范,用于区别于C/C++:

    1. 动态分配内存:C/C++没有动态分配需要手动分配

    2. GC内存回收:C/C++需要自己进行回收垃圾

:C/C++可以直接调用操作系统硬件,而JVM需要通过一层java虚拟机才行,所以一般来说C/C++的性能更好;

还有一点需要注意:Davilk虚拟机和JVM虚拟机的区别是在于,JVM是跨平台跨设备,而Davilk是安卓专属的虚拟机。

以上就是JVM与C/C++的区别 以及 JVM与Davilk区别;


接下来主要看JVM相关的内容:

JVM较重要的三层组成:类加载器、运行时数据区 、 执行引擎

        对于开发者来讲这三者中最主要的是运行时数据区,了解运行过程中程序的执行过程;

执行引擎跑的时候,运行时数据区数据产生

运行时数据区可分为两类:线程共享区 线程私有区

1、线程共享区:方法区、堆区。他们的生命周期和JVM一样;

2、线程私有:Java虚拟机栈、本地方法栈、程序计数器。仅供当前自身线程中使用;

Java内存分为五个区域:虚拟机栈、本地方法栈、方法区、堆、程序计数器

各个区域存储的内容:

    1. 方法区:类信息(class字节码信息:类名、作者、时间等)、静态变量、处理逻辑的指令集。方法区数据保存在主内存(内存条);

    2. 堆区:对象实例、全局变量(在一个类里面定义的不带static的变量或对象引用都是在此类的堆区)。堆区数据保存在主内存(内存条);

    3. 虚拟机栈:存储方法的局部变量,每次创建一个线程就会有一个虚拟机栈(生命周期与线程相同)栈区的数据运行在高速缓存中

    4. 程序计数器:用于计算cpu执行到哪一步了。运行在高速缓存中

    5.本地方法栈:用于调用native方法。运行在高速缓存中;

:Java虚拟机栈的读取速度仅次于程序计数器,而程序计数器就是一个寄存器,不会有内存泄露的问题,是基于栈架构;一个线程会创建一个java虚拟机栈,java虚拟机中会有一个或多个栈帧,一个栈帧就是一个方法的执行,每个栈帧包含:局部变量表、操作数栈、动态链接、返回地址。

如下:

分区类型案例

现在来看下这些分别在操作系统的什么位置:

    前提:由于CPU的运行速度很快大概在2.2GHZ,而内存条的运行速度较慢大概在1333MHZ,所在的运转速度差别很大,所以二者不能直接进行数据交互;因此两者需要交互就需要一个中介者,这个中介者就是高速缓存,也称为一级存储器;因此就涉及:CPU、高速缓存、内存条(主存);

      方法区堆区都在内存条中他们的内存大小是通过malloc(C里面专门分配内存的函数)来分配。

      虚拟机栈区本地方法栈以及程序计数器都是在高速缓存中。

简单的画了一个图供理解:

画的比较简陋

接下来了解一下Java的堆和栈:

不注重编写代码中会造成的错误:

        OutOfMemoryError是堆内存溢出,表示:他需要那么多内存但是系统不能给他分配,他就只能闹脾气崩掉。

        StackOverflowError错误表示栈内存崩溃了。

下面代码演示一下堆和栈报错的差别:

        定义了一个bean对象,里面有一个很大的byte数据,简单的看一下伪造的堆内存OOM现象;

堆内存造成的OOM 栈内存崩溃

以上两个不规范的代码导致的堆和栈崩溃的演示,供理解使用。


堆的理解

存储的都是对象,凡事new出来的都是对象,存储在堆内存中,堆中的实体对象不会和栈中的局部变量一样自动消毁,他会一直存在在堆中,当这个对象用不到了就会变成垃圾,所以有垃圾回收机制帮忙回收。

供理解堆区操作

补充:

图中S0-S1阈值15次,作为安卓的art虚拟机,内存互换的条件入老年代的阈值是6,其他的比如HotSpot虚拟机的阈值是15;

MajorGC是只回收old区域,而FullGC是回收eden区和old区;

MajorGC的执行速度是MinorGC的10倍;

一般面试提到的JVM虚拟机默认指代Hospot虚拟机;

Android这边为主的是Davilk和Art虚拟机;

如何避免内存抖动?内存抖动就是频繁的创建和销毁大量对象

不要在for循环里创建对象,可以复用对象

栈的理解

存储的都是局部变量,而局部变量是在方法里定义的,所以方法先进栈,然后在定义变量,局部变量的生命周期很短,变化很快。

供栈区理解,结合代码流程

下面我们举个例子看看堆和栈是如何存储的:

        在主函数main中创建一个数组 int [] arr = new arr[3] , 看是怎么分配在堆栈的。

        首先主函数main先入栈,因为方法都是在栈里,然后arr入栈:

主函数main入栈,接着arr入栈

        然后看等号右边new arr[3] ,new 表示一个对象实例,所以应该在堆中,所以通过new在堆中开辟一块内存

在堆中开辟内存,都会进行默认初始化,不然不能使用

解释上图:前面一列是索引,后面是值,会给这个实体分配一个内存地址,数组这个实体在堆内存中产生之后每个空间都会默认初始化,这是堆内存的特点,未初始化的数据是不能用的,但是在堆里是可以的,因为初始化过了,但是在栈里面没有。所以,堆和栈里就合作创建了变量和实体;

堆内存分配了一个地址,这个地址赋给arrarr就通过这个地址指向数组,所以arr想操作数组就用这个地址,而不是直接把实体都赋值给它,所以这个叫引用数据类型,而不是基本数据类型了,arr引用了堆内存中的实体;

垃圾数据产生:

当一个实体没有引用数据类型指向的时候,也就是new了一个arr数据,但是没有int arr[]指向。这个时候这个实体在堆内存中就不会被释放而被当做一个垃圾,在不定时的时间内被java垃圾回收机制自动回收。自动回收机制会检测堆中是否有垃圾,有就回收,但是不定时;

堆的分类:

堆占用的内存最大,所以java回收机制主要作用在堆上。正是因为GC的存在,现代的收集器都采用分代收集算法,堆又被细化分为新生代老年代。新生代又分为:Eden空间、From SurvivorS0)空间、To SurvivorS1)空间:

堆内存的分配

后续会详细分析堆内存的垃圾回收机制的原理。

上一篇 下一篇

猜你喜欢

热点阅读