网站百度关键词优化,wordpress qq群,检察院网站建设情况,网站建设中 目录是什么第二章 Java内存区域与内存溢出异常
2.1 意义
对于C、C程序开发来说#xff0c;程序员需要维护每一个对象从开始到终结。Java的虚拟自动内存管理机制#xff0c;让java程序员不需要手写delete或者free代码#xff0c;不容易出现内存泄漏和内存溢出问题#xff0c;但是如果…第二章 Java内存区域与内存溢出异常
2.1 意义
对于C、C程序开发来说程序员需要维护每一个对象从开始到终结。Java的虚拟自动内存管理机制让java程序员不需要手写delete或者free代码不容易出现内存泄漏和内存溢出问题但是如果出现了内存泄漏和溢出的问题就需要知道虚拟机是怎样使用内存的才能排查错误并且修正。本章主要讲明Java虚拟机内存的各个区域各个区域的作用、服务对象以及常见的异常。
2.2 运行时数据区域
Java虚拟机在执行Java程序的过程中会把管理的内存分为几个不同的数据区域其中包括以下几个运行时数据区这些区域有各自的用途以及创建和销毁的时间。
2.2.1 程序计数器 程序计数器占用了一块较小的内存空间充当线程指示器的角色。 Java虚拟机的多线程是通过线程之间切换、分配处理器执行时间实现的一个内核在某一时刻只会执行一条线程的指令所以计数器就可以在切换线程时发挥作用能够确定切换到正确位置继续程序执行。而且要保证每个线程的计数器是独立的互不干涉。 如果执行的Java方法计数器记录的是正在执行的虚拟机字节码的地址。如果是native方法计数器为空undefined。 唯一一个没有OutOfMemoryError情况的区域
2.2.2 Java虚拟机栈 线程私有的生命周期与线程相同。 虚拟机栈对应Java方法执行每个方法对应栈中的一个栈帧每个方法被调用到执行完毕的过程就对应一个栈帧在虚拟机栈中入栈到出栈这个栈帧存储局部变量表、操作数栈、动态连接、方法出口等信息。 局部变量表存放编译期可知的基本数据类型boolean、byte、char、short、int、float、long、double、对象引用reference类型和returnAddress类型。 局部变量表的的存储空间以局部变量槽slot表示long和double占两个slot其他类型一个slot。 栈帧在编译期就给局部变量表分配好了内存空间即slot的数量在方法运行期间不会改变。 两类异常线程请求的栈深度大于虚拟机的所允许的深度抛出StackOverflowError异常 Java虚拟机栈容量可以动态扩展的情况下扩展时无法申请到足够的内存会抛出OutOfMemoryError异常
2.2.3 本地方法栈
与虚拟机栈执行Java方法相似只不过本地方法栈执行Native方法。没有强制规定有的Java虚拟机Hot-Spot直接合二为一。
2.2.4 Java堆
所有线程共享虚拟机启动时创建唯一目的是存放对象实例几乎所有的对象实例都在堆上分配内存是垃圾收集器管理的内存区域基于分代收集理论逻辑上分为“新生代”“老年代”“永久代”“Eden空间”“From Survivor空间”“To Survivor空间”等。可以处于物理不连续的内存空间但逻辑上是连续的。参数-Xmx设置 Java 堆内存的最大大小和-Xms设置初始堆内存大小可以控制是否可扩展如果堆中没有内存完成实例分配且堆无法扩展会抛出OutOfMemoryError异常。
2.2.5 方法区
各个线程共享别名“非堆”用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据可以不实现垃圾回收主要因为回收效果不太好回收条件苛刻但有时有必要无法满足新的内存分配会抛出OutOfMemoryError异常*方法区的实现可以由JVM实现自由决定JDK8以前称为永久代但不等价为了让垃圾收集器方便管理这部分内存。JDK8以后改为元空间
2.2.6 运行时常量池
方法区的一部分存放编译期生成的字面量比如字符串或基本类型的常量和符号引用类和接口的全限定名例如java.lang.String。字段的名称和描述符例如int age中的字段名称age和类型描述符int。方法的名称和描述符例如void print(String s)中的方法名称print和描述符(Ljava/lang/String;)V一般会把符号引用翻译的直接引用也存储在运行时常量池中具备动态性运行期间也可以放入新的常量String类的intern()方法无法申请到内存会抛出OutOfMemoryError异常
2.2.7 直接内存
不是运行时数据区的一部分使用Native函数分配堆外内存再通过堆内的一个对象DirectByteBuffer引用这块内存来使用提高性能也会出现OutOfMemoryError异常
2.3 Hotspot虚拟机对象探秘
HotSpot虚拟机在Java堆中对象分配、布局和访问的全过程
2.3.1 对象的创建过程
new一个对象时Java虚拟机会首先检查这个指令的参数能否在常量池中定位到一个类的符号引用然后判断是否已经被加载、解析和初始化过。如果没有则先执行类加载过程然后为新生对象分配内存。 如果内存规整一边是使用过的内存一边是空闲的内存由指针分隔那么分配内存就只需要移动指针来完成这就是指针碰撞。 实际上内存并不规整所以虚拟机通过一个列表来记录哪些内存块是可用的然后从列表中找到一块空间分配给对象实例这就是空闲列表。 堆是否规整由垃圾收集器有没有整理能力决定。
为了并发情况下保证创建对象的线程安全有以下两种方案一般情况本地缓冲区用完再进行同步处理。
对分配内存的行为进行同步处理实际中虚拟机采用CASCompare-And-Swap比较当前值和预期值如果它们相等则将当前值更新为新值。否则什么也不做。失败重试当多个线程同时尝试更新同一个变量时只有一个线程的CAS操作会成功其他线程会失败。失败的线程不会阻塞而是重新读取变量的当前值并再次尝试操作每个线程在堆中预先分配一块内存称为本地线程分配缓冲Thread Local Allocation BufferTLAB
分配完后将分配到的内存初始化为零值不包括对象头。然后根据对象头里的信息进行设置。接着执行构造方法()方法 一个完整的对象就被创建出来了。
2.3.2 对象的内存布局
一个对象在堆中的布局为三个部分对象头header、实例数据Instance data、对齐填充Padding
对象头包括两类信息 Mark Word对象自身的运行时数据动态数据结构类型指针指向类型元数据确定该对象是哪个类的实例。使用句柄访问则不需要保存类型指针类型指针都存到了句柄池中如果是数组还需要记录数组长度 对象真正存储的有效信息各种类型的字段内容。 存储顺序由虚拟机和java源码决定父类定义的变量会在子类之前 对齐填充起占位符的作用保证8字节的整数倍
2.3.3 对象的访问定位
由虚拟机实现决定主流有两个使用句柄或者直接指针
使用句柄reference存储对象的句柄地址句柄指向实例数据和类型数据。对象被移动时只会改变句柄中的指针而reference不需要修改如图 直接指针对象自身保存指向类型数据的指针不需要多一次间接指针的开销hotspot使用。如图 2.4 OutOfMemoryError异常
验证运行时区域存储的内容以及遇到内存溢出异常时通过信息得知是哪个区域的内存溢出知道怎样的代码可能会导致这些区域内存溢出以及出现这些异常后该如何处理。
2.4.1 Java堆溢出
参数-XXHeapDumpOnOutOf-MemoryError可以让虚拟机在出现内存溢出异常的时候Dump出当前的内存堆转储快照以便进行事后分析
Java堆内存的OutOfMemoryError异常会跟随进一步提示“Java heap space“ 处理方法 通过内存映像分析工具对dump出来的堆转储快照进行分析确认导致oom的对象是否必须内存泄露还是内存溢出 如果内存泄露 查看泄露对象到GC roots的引用链定位对象创建的位置找到出现内存泄漏的代码的具体位置 如果内存溢出 检查堆参数设置查看是否还有调整空间检查是否某些对象生命周期过长、持有状态过长或存储结构设计不合理
2.4.2 虚拟机栈和本地方法栈溢出
Java虚拟机规范中描述了两种异常
如果线程请求的栈深度大于虚拟机所允许的最大深度将抛出StackOverflowError异常如果虚拟机的栈内存允许动态扩展当扩展栈容量无法申请到足够的内存时将抛出 OutOfMemoryError异常。
对于HotSpot虚拟机不区分虚拟机栈和本地方法栈所以栈容量只由-Xss设定而且不支持栈的动态扩展。所以除非创建线程时就内存不足才会出现OutOfMemoryError异常。只会在运行时栈无法容纳新的栈帧抛出StackOverflowError异常。
如果出现了建立过多的线程导致的内存溢出可以考虑减少最大堆和减少栈容量来换取更多的线程。因为windows给一个进程分配的内存是有限的。
2.4.3 方法区和运行时常量池溢出
首先明白在HotSpot中方法区表现形式的变化如下图 *方法区的实现可以由JVM实现自由决定尽管JVM规范将方法区描述为非堆内存区域但运行时常量池在Java 8之后确实被放置在堆内存中。这意味着运行时常量池虽然是方法区逻辑上的一部分但在实际的JVM实现中它的存储位置可以是堆内存。字符串常量池同理
为什么运行时常量池在堆内存中
动态性运行时常量池中的常量不仅包括类文件中定义的常量还可以在运行时动态添加。这种动态性需要灵活的内存管理堆内存提供了这样的灵活性。垃圾回收运行时常量池中的常量可能在程序运行过程中变得不再需要。将其放在堆内存中JVM可以利用堆内存的垃圾回收机制更高效地回收这些不再使用的常量。
回到溢出问题String::intern()是一个本地方法作用是如果字符串常量池中已经包含一个等于此String对象的字符串则返回代表池中这个字符串的String对象的引用否则会将此String对象包含的字符串添加到字符串常量池中并返回引用。
jdk7之前运行时常量池属于方法区且大小固定容易溢出。之后转移到堆上以后不容易溢出。
2.4.4 本机直接内存溢出
直接内存Direct Memory的容量大小可通过-XXMaxDirectMemorySize参数来指定如果不去指定则默认与Java堆最大值由-Xmx指定一致
直接内存导致的内存溢出一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况如果内存溢出之后产生的Dump文件很小而程序中又直接或间接使用了DirectMemory典型的间接使用就是NIO有可能是直接内存的问题。
从GC Roots找引用链Stop The World因为如果对象引用关系还在不停变化会影响收集的准确性虚拟机应当可以直接得到哪些地方记录了对象引用比如OopMap会记录栈和寄存器里引用的位置。