前言
要了解Java虚拟机首先要知道的基础就是内存。虚拟机存在的意义就是对内存进行管理,因为不用人为的去管理每个对象的内存,所以才让java使用起来那么方便,不用像c、c++那样去free。
运行时数据区
Java所管理的内存划分为不同的区域,每个区域都有自己的用处。
1、程序计数器PC
如果你学过汇编的话就很好理解,在计算机中指令是一条条执行的,PC就是记住当前执行到哪一行了。需要注意的是,每条线程都需要一个独立的PC,且互相之间不影响。
2、虚拟机栈
栈是一个在程序设计的时候很常见的一个数据结构,有一定计算机基础的话,你知道递归的实现其实就是利用栈,也知道递归的深度过大会导致栈溢出。而在Java虚拟机中栈是描述Java方法执行的内存模型,每个方法的执行就是虚拟机中一个入栈出栈的过程。
3、本地方法栈
这个栈和虚拟机栈一样,只是这个栈是一个VIP,只有native方法才会使用到这个栈,那什么是native方法呢?java方法可能会调用到非java-code的方法,如c的方法,这样的方法称为native方法。
4、堆
堆是最重要的一块也是比较复杂的一块区域,因为几乎所有的对象实例都在堆里分配内存。堆是垃圾回收的主要区域,因为很多用过之后没有用的对象都在这里了,它们所占用的内存都需要被回收。
5、方法区
用于存储已经被虚拟机加载的类信息、常量、静态变量等。看到它的功能基本可以想象到,它存放的东西基本上都属于那种不可改变而且一直存在的量,所以这个区域有时被称为“永久代”但是记住它们不一定都是永久的,这个区域也会被回收,只是相对来说没有像堆那样。
对象创建
内存是用来存放对象的,那么java是如何创建一个对象的呢?首先我们知道创建一个对象就是new出来,虚拟机在new之前还要做的事情是确定这个类是否已经被正确加载了。
接下来就是去分配内存了,当类被加载完成之后,我们就知道这个对象需要多大的内存了。如果内存是完整的,那就像切蛋糕一样切出一块来,如果内存是分散的,那就需要找一块足够大的切了。而内存是分散的还是完整的取决于垃圾回收的算法。
然后设置对象的参数,如这个对象是哪个类的实例,哈希码,GC分代年龄等。
最后再执行对象的init方法给每个需要的字段进行赋值,然后根据这个对象的构造方法进行初始化。
对象的访问
那么我们创建了一个对象之后,我们如何访问这个对象呢?这个时候就用到了我们的栈了,栈记录了一个对象的引用reference。这里有两种方式,第一种是这个引用数据保存了句柄数据然后堆中有一个句柄池其中记录了对象实例数据的指针,我们通过引用找到句柄池中的位置,再通过指针找到对象。第二种是指针访问,也就是直接储存了对象的地址。句柄的好处是,对象移动时reference不用改变,指针的好处是速度快。
感受虚拟机
我们运行两个程序来直观感受一下虚拟机到底做了什么事情
首先运行下面这个程序
需要注意的是,需要调整一下虚拟机的参数
Xms是JVM初始分配的堆内存
Xms是JVM最大允许分配的堆内存
然后运行就能看见异常Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
然后在相同的条件下我们运行第二个程序
你会发现没有什么异常,然后你需要做的是,对比两个程序,看看这两个程序有什么不一样,这样的不一样为什么会导致这样的结果。理解了,你就知道java虚拟机到底在做什么事情了。
上面的测试是模拟了堆溢出的情况,还有方法区溢出,常量池溢出,栈溢出等,出现StackOverflowError或者OutOfMemoryError。
我们所要知道的是出现这样错误是什么样的原因导致的,以上就是在内存方向上虚拟机相关。