java自动内存管理机制

java技术体系

java程序设计语言
各种硬件平台上的java虚拟机
Class文件格式
java API类库
来自商业机构和开源社区的第三方java类库
## java发展史 ##

java内存区域与内存溢出异常

## Java内存区域 ##
    java虚拟机栈
    java虚拟机堆
    java虚拟机程序计数器
    java虚拟机方法区
    java虚拟机本地方法栈
## 程序计数器 ##
    它可以看作是当前线程所执行的字节码的行号指示器。
    每个线程都需要一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储,线程私有。
    此内存区域是唯一一个没有规定任何OutOfMemoryError情况的区域。
## java虚拟机栈 ##
    线程私有,生命周期和线程相同。
    虚拟机栈描述的是java方法执行的内存模型。(每个方法在执行的同时都会创建一个栈帧,用来存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。)
    在线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError。现在虚拟机基本都可以动态扩展,但是当扩展也不能达到所需要求的时候回抛出OutOfMemoryError异常。
## java本地方法栈 ##
    本地方法栈与栈发挥的作用类似,区别就是栈是为java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务。
    会有两种异常:StackOverflowError和OutOfMemoryError。
## java虚拟机堆 ##
    唯一目的就是:存放对象实例(几乎所有的对象实例)。
    java堆使垃圾收集器的主要区域,一次很多时候也会被叫做为GC堆。
    现在收集器基本都采用分代收集算法,java堆可以分为新生代和老年代。
    堆中没有内存完成实例分配,并且无法拓展的时候,将会抛出OutOfMeMoryError。
## java虚拟机方法区 ##
    方法区和堆一样,使各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    很多人也把方法区称之为永久代。但是两者并不等价,只是把GC分代收集的方法扩展用到了方法区而已,但是方法区垃圾回收很少。最多的就是常量池的回收和对类型的卸载。
    该方法区也存在OutOfMemoryError异常。
## 方法区之运行时常量池 ##
    运行时常量池使方法区的一部分,Class文件中除了有类的版本,字段接口等等,还有常量池。
    用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放。
    内存不足会产生OutOfMemoryEroor。
## 直接内存 ##
    直接内存不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是也会触发OutOfMemoryError异常。
    在NIO类中,引入了一种基于通道和缓存区的I/O方式,它可以使用Native函数库直接分配内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
    直接内存分配不受java堆的大小的限制。

HotSpot虚拟机对象的探秘

## 对象的创建 ##
    虚拟机遇到new指令,先检查指令参数是否在常量池,并检车这个类是否被加载,解析,初始化过,如果没有则先执行相应的类加载。
    类加载检查通过后,虚拟机为新生对象分配内存,首先划分内存空间,然后考虑对象创建在虚拟机中是否频繁操作,并发情况下容易产生线程安全问题,所以先进行同步处理。
    内存分配完之后,虚拟机将内存空间初始化为0(不包括对象头)。
    接下来虚拟机对对象进行必要的设置,比如这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的gc分代年龄等。这些信息存放于对象头中。
    对象创建之后需要init方法来按照程序员所需进行初始化。
## 对象的内存布局 ##
    对象在内存布局中可以分为三个区域:对象头,实例数据,对齐填充。
    HotSpot虚拟机对象头包括两部分:存储对象自身的运行时数据(例如哈希码,GC分代年龄,锁状态标志,偏向线程ID等等),类型指针(就是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例)。
    实例数据部分:对象真正存储的有效信息。
    对齐填充:并非必然存在,没有特别含义,仅仅起着占位符作用。
## 对象的访问定位 ##
![句柄访问方式](https://img-blog.csdn.net/20180905155659897?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNzg0MTA1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
![指针访问方式](https://img-blog.csdn.net/20180905155804188?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNzg0MTA1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
句柄的好处在于reference的存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身并不需要修改。
指针访问方式最大的好处就是速度更快,节省了一次指针定位的时间开销。HotSpot就是用指针进行对象访问的,但是从整个软件开发来讲,各种语言和框架使用句柄来访问也十分常见。

OutOfMemoryError异常

在java虚拟机中除了程序计数器都有OOM异常(这个异常的简称)。
目的:以后出现内存溢出异常,我们可以快速知道什么代码导致,已经如何处理。
## java堆异常 ##
    java堆是用来存储对象实例的,只要不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象适量到达最大堆的容量限制之后就会产生OOM异常。
    解决方案:
        第一方案首先想到内存映像分析工具对Dump出来的堆转储快照进行分析,重点是分析内存中的对象是否必要的,另一个意思也就是引发此异常的到底是内存泄漏还是内存溢出。
        如果是内存泄漏,那么用工具查看泄漏对象到GC Roots的引用链,就可以找到泄露对象的什么路径与GC Roots相关联并导致垃圾收集器无法回收,就可以找出问题位置。
        如果是内存溢出,那就是还要让溢出部分存储,那么就要去检查虚拟机的堆参数,然后与机器物理内存对比看看能不能调大一点,或者从代码上看是不是有的对象生命周期过长,尝试减少程序运行期的内存消耗。
## java虚拟机栈和本地方法栈溢出 ##
    在HotSpot不区分虚拟机栈和本地方法栈,在这就一起讲了。
    有两种异常:OOM(虚拟机扩展的时候无法申请到足够的内存空间)和StackOverflowError(线程请求的栈深度大于虚拟机所允许的最大深度)。
## 方法区和运行时常量池溢出 ##
    方法区溢出也是一种常见的内存溢出异常,一个类被垃圾收集器回收掉,判定条件是比较苛刻的,在经常产生大量Class的应用中,需要特别注意类的回收状况。
## 本机直接内存溢出 ##
    由DircetMemort导致的内存溢出,一个明显特征是在Heap Dump文件中不会看见明显的异常,如果读者看见OOM之后的Dump文件很小,而程序中有直接或间接使用了NIO,就可以考虑是不是这方面的原因。

猜你喜欢

转载自blog.csdn.net/qq_42784105/article/details/82423141
今日推荐