这篇来分析一下,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现象;


以上两个不规范的代码导致的堆和栈崩溃的演示,供理解使用。
堆的理解
存储的都是对象,凡事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入栈:

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

解释上图:前面一列是索引,后面是值,会给这个实体分配一个内存地址,数组这个实体在堆内存中产生之后每个空间都会默认初始化,这是堆内存的特点,未初始化的数据是不能用的,但是在堆里是可以的,因为初始化过了,但是在栈里面没有。所以,堆和栈里就合作创建了变量和实体;
堆内存分配了一个地址,这个地址赋给arr,arr就通过这个地址指向数组,所以arr想操作数组就用这个地址,而不是直接把实体都赋值给它,所以这个叫引用数据类型,而不是基本数据类型了,arr引用了堆内存中的实体;
垃圾数据产生:
当一个实体没有引用数据类型指向的时候,也就是new了一个arr数据,但是没有int arr[]指向。这个时候这个实体在堆内存中就不会被释放,而被当做一个垃圾,在不定时的时间内被java垃圾回收机制自动回收。自动回收机制会检测堆中是否有垃圾,有就回收,但是不定时;
堆的分类:
堆占用的内存最大,所以java回收机制主要作用在堆上。正是因为GC的存在,现代的收集器都采用分代收集算法,堆又被细化分为新生代和老年代。新生代又分为:Eden空间、From Survivor(S0)空间、To Survivor(S1)空间:

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