第2章 Java内存区域与内存溢出异常

简写

OOME:OutOfMemeryError


SOFE:StackOverflowError

2.2 内存数据区域

第2章 Java内存区域与内存溢出异常

2.2.1 程序计数器

程序计数器(Program Counter Register)是一块小的内存空间,是 当前线程所执行的字节码的行号指示器


多线程的情况下,各个线程拥有独立计数器。


执行Java方法时,指向字节码指令地址,Native方法时为Undefined(此处没有规定OOME:JVM规范)。

2.2.2 Java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stacks),线程私有。


定义:方法执行的内存模型


创建栈帧(Stack Frame),存储局部变量表、操作数栈、动态链接、方法出口等信息。

局部变量表

局部变量表:编译期可知的基本数据类型、对象引用和returnAddress(指向字节码指令地址)。


64位long和double占用2个局部变量空间(Slot),其他1个。


其空间在编译期完成分配。

异常规定

SOFE:线程请求的栈深度大于虚拟机允许的深度。


OOME:无法申请到额外的内存

2.2.3 本地方法栈

本地方法栈(Native Method Stack)。


为Native方法提供服务。


可能与JVMS合一(Sun Hotspot)。


同样也会抛出SOFE,OOME。

2.2.4 Java堆

所有线程共享。


作用:存放对象实例和数组,不是绝对(见11章11.3.5节后期优化相关)。


GC主要区域


细分:新生代,老年代。(Eden,From Survivor,To Survivor)


堆可以处于物理不连续的空间。(逻辑连续即可)


OOME: 内存不足且无法扩展时。

直接内存

NIO里使用Native函数库分配堆外内存。


通过堆中的DirectNyteBuffer对象作为引用进行操作。


好处:避免在Java堆和Native堆中来回复制数据。


可能出现OOME。


详细介绍见第3章

2.2.5 方法区

方法区(Method Area)。


作用:存储加载的类信息、常量、静态变量、即时编译代码等。


是堆的一个逻辑部分,别名Non-Heap(非堆)。


别名:永久代,但本质上不等价。Hotspot基于永久代实现方法区,且GC可以扩展过去。


规范指明:可以不实现GC。


OOME:内存不足。

2.2.6 运行时常量池(方法区)

方法区的一部分。


作用:存放字面量和符号引用。


运行期也可以放入,如String类的intern()方法。


会抛出OOME

概念解释

字面量:字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。比如字面量3,也就是指3。再比如 string类型的字面量"ABC", 这个"ABC" 通过字来描述, 所以就是字面量。


符号引用:


一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。


直接引用:


(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)


(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)


(3)一个能间接定位到目标的句柄

2.3 HotSpot虚拟机管理对象过程

创建

  1. 检查类加载情况;(见7章)
  2. 从堆分配内存
    1. 内存规整时,指针直接移位,称为“指针碰撞 Bump the Pointer”。
    2. 不规整时,需要维护列表,称为“空闲列表 Free List
    3. 方式由内存是否规整确定,内存规整由GC器是否带有压缩整理功能确定。
    4. 如何保证线程安全:
      1. CAS
      2. 隔离分配,每个线程拥有一部分本地线程分配缓冲。

  3. 初始化为零值
  4. 初始化对象头:类的元数据
  5. 执行对象的

目录,HotSpot解释器代码片段

对象内存布局

组成:对象头,实例数据,对齐填充。

对象头

包含两部分

  1. Mark Word:hashcode、GC分代年龄、状态、线程持有锁、偏向锁(线程)id
  2. 类型指针:指向类元数据,非必须。
  3. 对于数组包含长度。

MarkOop


第2章 Java内存区域与内存溢出异常

偏向锁:


作用:在单线程(非并发情况,即没有竞争锁的时候)访问同步代码块的时候,可以忽略同步锁机制,来提升性能,


举例:


比如有一线程A,第一次访问同步代码块,申请锁对象,拿到锁对象后,把线程A的ID 写入对象头即偏向线程ID,会把锁对象的状态改为01,即偏向锁状态,此时线程A再次进入同步代码块的时候,则直接忽略掉同步代码块,这样就达到提高性能的作用。(在并发时,偏向锁是多余的们也可以理解为不存在的,因为他会自动升级为轻量级锁,和重量级锁,)

实例数据

各数据字段数据

对齐填充

HotSpot 要求起始地址为8字节的整数倍

2.3.3 对象访问定位

实现方式:

  1. 句柄:reference中存储稳定的句柄地址,在对象移动时(GC)无需修改regerence
  2. 直接指针:访问实例速度更快。(HotShot采用)

第2章 Java内存区域与内存溢出异常 第2章 Java内存区域与内存溢出异常

2.4 OOME测试

工具:https://www.eclipse.org/mat/


Xms:堆最小值


Xmx:堆最大值


HeapDumpOnOutOfMemoryError:OOME时保存快照

import java.util.ArrayList;
import java.util.List;
/**
 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 * @author ichukai
 */
public class HeapOOM {
    static class OOMObject{
    }
    public static void main(String[] args){
        List<OOMObject> list=new ArrayList<OOMObject>();
        while (true){
            list.add(new OOMObject());
        }
    }
}

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid83183.hprof ...
Heap dump file created [27724413 bytes in 0.178 secs]

2.4.2 虚拟机栈和本地方法栈溢出SOF

无限递归栈溢出

stack length:774
Exception in thread "main" java.lang.StackOverflowError

创建线程导致内存溢出

java.lang.OutOfMemoryError: unable to create new native thread
  1. 可以通过减少最大堆来换取更多线程

code

不要测试线程代码,会导致系统卡死

/**
 * VM Args: -Xss160k
 */
public class JavaVmStackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    private static void endlessLoop() throws Throwable {
        JavaVmStackSOF javaVmStackSOF = new JavaVmStackSOF();
        try {
            javaVmStackSOF.stackLeak();
        } catch (Throwable t) {
            System.out.println("stack length:" + javaVmStackSOF.stackLength);
            throw t;
        }
    }
    private static void leakByTread() {
        while (true){
            Thread thread=new Thread(new Runnable() {
                public void run() {
                    while (true){
                    }
                }
            });
            thread.start();
        }
    }
    public static void main(String[] args) throws Throwable {
        endlessLoop();
//       leakByTread(); //不要测试线程代码,会导致系统卡死
    }
}

2.4.3 方法区和常量池溢出

JDK7此方法已失效

import java.util.ArrayList;
import java.util.List;

/**
 * VM args: -XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args){
        // 保持常量池引用
        List<String> list=new ArrayList<String>();
        int i=0;
        while (true){
            list.add(String.valueOf(i++).intern());
        }
    }
}

String.intern():

jdk7下前者为true,因为intern方法不再把值放到常量池,而是放实例引用,而后者“java”已经有引用了,所以不是同一个。

public class StringInternTest {
    public static void main(String[] args){
        String str1=new StringBuilder("软件").append("开发").toString();
        System.out.println(str1==str1.intern()); 
        String str2=new StringBuilder("ja").append("va").toString();
        System.out.println(str2==str2.intern());
    }
}

动态类导致方法区溢常

通过CGLib填充动态类信息,可导致OOM。


动态产生的大量JSP应用


基于OSGi应用,被不同加载器加载。

直接内存溢出

由DirectMemory导致的内存溢出,在dump不会有明显异常,使用了NIO可以排查。


MaxDirectMemorySize 默认与Xmx(堆最大值)一样。


测试下来无效,后面再去看Java 规范

import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
 * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
 */
public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}

原文 

http://dev.chukai.pro/post/shen-ru-li-jie-javaxu-ni-ji/di-2zhang-javanei-cun-qu-yu-yu-nei-cun-yi-chu-yi-chang

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 第2章 Java内存区域与内存溢出异常

赞 (1)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址