Java 7 VM:hotspot VM

java8 VM:hotspot VM(吸收了JRockit VM的部分优点)

内存溢出:简单的说就是在创建对象或其它行为申请不到需要的内存大小(申请内存大于实际内存)

内存泄露:简单的说就是所创建的实例化对象由于种种原因未被gc回收导致内存不足(实例化对象填满了内存空间)

新生代与老年代:刚创建的对象数据一般都是属于新生代(有些较大的为了减少从新生代到老年代的数据拷贝回直接进入老年代),当经历了15次gc(对象数据的age)依旧没有被回收时就会进入老年代;除此之外如果老年代中有过半的对象数据age在同一水平,那么新生代进入老年代的标准就可以不再是15,而是这一过半的水平。

一、内存区域的划分:

    (一)、程序计数器:字节码解释器通过改变计算器的值来选取所要运行的字节码指令,并由计算器记录字节码指令的地址;所占有的内存较小,由于cpu每次只会处理一条线程(即便是在并行的情况下,进程也是同理,看上去是多个软件同时运行,实则是根据具体的使用情况断断续续的通过占用cpu进行运作),一般不会产生内存溢出或泄露。

    (二)、Java虚拟机栈:用于存放对象的引用;在Java中,虚拟机栈(用于执行Java方法)与本地方法栈(用于执行Native方法),如果没有设置大小则会自动扩容。

    (三)、Java堆:与栈相对应,用于存放对象具体的数据,主要可以划分为新生代和老年代(堆=新生代+老年代);是垃圾回收的主要区域,因此也被称为“GC堆”(垃圾堆),所有线程所共享。

    (四)、方法区:用于存放类信息、常量、静态变量以及编译时所产生的数据,这个区域也被人称之为永久代,之所以被称为永久代是相对于gc而言(原因应该是由于方法区中的一些信息从程序运行到结束一直存在,因此这个所谓的“永久”也是相对于程序运行而言),但实际上永久代与方法区并不等价,自Java7开始“去永久代”(永久代的存在容易导致内存泄露),可以说方法区是Java堆的一个逻辑区域(但实际上方法区并不属于Java堆),该区域由所有线程共享。

    (五)、常量池:顾名思义,是用于存放常量的;该区域一开始在方法区当中,之后就放进了Java堆当中进行使用。

    (六)、直接内存:本身并不属于虚拟机内存,但在一些程序运行时也会用到,主要是用于存放缓冲流缓冲区的数据,避免从Java堆与native堆中来回cp数据从而显著提高运行效率。

二、对象的创建:

    (一)、首先会在栈中创建对象的引用。

    (二)、其次会在堆中查找是否有该对象所对应的数据,如果没有则创建,在堆中创建对象的数据有两种方式,至于是哪种主要取决于堆中的数据是否规整;如果规整,则用指针将已使用区与空闲区分隔开来,当创建对象数据时指针就会向空闲区移动为数据腾出空间,因此这种方法也被称之为“指针碰撞”;如果不规整,虚拟机会维护出一个列表,列表中记录了哪些区域是已用的哪些是空闲的,当创建对象数据时就会划分出足够大的空间用于存放数据,因此这种方法也被称之为“空闲列表”。

    (三)、最后,要通过对象的引用来使用对象数据就需要把它们关联起来,主要的方法有使用句柄与直接指针两种;如果是使用句柄,就会在Java堆中创建一个句柄池,句柄池中包含了对象数据和数据类型的地址,具体如图所示(图片来自《深入理解Java虚拟机》):

java调整老年代 java老年代增长过快_数据

如果使用的是直接指针,那么就是在对象的引用中存放了对象数据的具体地址,具体如下图所示(图片来自《深入理解Java虚拟机》):

java调整老年代 java老年代增长过快_Java_02

两种方法的优劣比较:使用句柄的好处是当对象的数据改变时,不需要直接移动引用的指针,只要移动句柄池指向对象数据的指针即可,而直接指针则需要直接移动引用指向对象数据的指针,但相比之下却少了根指针的指向,因此对象的创建的速度会比较快;Java主要的虚拟机hotspot使用的便是直接指针。

三、内存溢出

    (一)、栈内存溢出:该内存溢出可以分为两种情况,一种是单个线程请求栈空间过大,从而抛出StackOverFlowerErr;另一种是栈内存扩容时申请的内存得不到满足从而抛出OutOfMemoryError,而之所以会出现这种状况是由于多线程运行下,线程本身占用了大量的内存导致的,因此该内存溢出并不是栈内存过小导致的,恰恰相反,栈内存越大,多线程运行下抛出该异常的可能性也就越大;但从另外一个角度看,这两种情况在本质上其实可以说是同一种情况的两种说法,单线程向栈空间申请内存,栈空间本身不能满足,于是进行扩容;当然,这是在没有设置栈大小的前提下。

    (二)、除了程序计数器以为,其它所有的内存空间都有可能出现内存溢出,从而抛出OOM异常。