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

运行时数据区域

一、程序计数器

程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。

虚拟机多线程中,为了线程来回切换可以找到各自的位置,各自都需要计数器。

如正在执行的是Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址

如正在执行的是Native方法,计数器记录为空

此内存区域是唯一一个没有OutOfMemoryError

二、java虚拟机栈

描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至自行完成的过程,就对应着一个栈帧在虚拟机中的入栈与出栈的过程。

局部变量表中存放了编译期可知的各种基本数据类型、对象引用(不等于对象本身)等。

局部变量表所需的内存空间在编译期间完成分配,运行时不会改变大小。

存在两个异常:

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

OutOfMemoryError:如果虚拟机栈可以动态扩展(大部分都可以扩展),扩展时无法申请到足够的内存。

三、本地方法栈

 虚拟机栈为虚拟机执行java方法服务;本地方法栈为虚拟机使用到的Native方法服务。

存在两个异常:

StackOverflowError OutOfMemoryError

四、java堆

最大的一块区域

唯一的目的存放对象实例

垃圾回收器管理的主要区域

可以处于物理上不连续的内存空间,逻辑上连续即可

可扩展通过(-Xmx和-Xms控制)

堆无法扩展抛出OutOfMemoryError

五、方法区

用于存储已被虚拟机加载的类信息、常量、静态变量等数据

堆的一个逻辑部分

很多人称为“永代区”,但不准确,字符串常量池已经移除

回收的效率不高但是有必要进行

方法区无法满足内存分配时,抛出OutOfMemoryError

六、运行时常量池

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放前一期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

注:直接内存(并不是运行时数据区)

基于通道与缓冲区(NIO)此处需要扩充看

避免了在java堆和Native堆中来回复制数据

HotSpot虚拟机对象探秘

一、对象的创建

1.虚拟机遇到一条new指令,首先检查常量池能否找到类的引用符号,并检查这个引用代表的类是否已被加载、解析和初始化。如果没有先加载类

2.加载后为新生对象分配内存,大小在加载后即确定

(1)分配有两种方法

“指针碰撞”(java堆中内存绝对规整,用过的放一边,空闲的放一边,中间放指针,要用就挪动指针)

“空间列表”(不规整,虚拟机就维护一个列表)

是否规整由采用的垃圾收集器是否压缩整理功能决定

(2)对象创建在虚拟机中是非常频繁地行为,并发情况下并不安全

两种方案:

对分配动作同步处理

把内存分配工作按线程划分在不同的空间进行,即每个线程预先分配一小块内存,成为本地线程分配缓冲(TLAB),哪个线程要分配,就在TLAB上分配

3.内存分配后虚拟机要将分配到的内存空间都初始化为零值,(保证了)java代码中不赋初始值就可以使用

4.虚拟机要对对象进行设置,如对象是哪个类的实例,对象的哈希码等,这些信息都在对象头中

5.从虚拟机的视角,一个新的对象已经产生了,但从java程序,<init>还没执行,所有字段都为0,所以执行new之后,接着执行<init>,进行初始化,这样对象才算产生

二、对象的内存布局

3块区域:对象头、实例数据(真正的有效信息)、对齐填充(占位作用)

三、对象的访问定位

两种方式:

使用句柄:java堆划分出块内存作为句柄池,reference(操作对象)中存储的就是句柄地址

直接指针:直接地址

优点:

句柄:稳定

指针:速度快

OutOfMemoryError异常

一、java堆溢出

对象数量达到堆的容量限制

不可扩展(将堆的最小值-Xms参数与值-Xmx参数设置为一样即可避免堆自动扩充)

通过(-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时,通过dump进行分析)

解决办法:通过内存影响分析工具对Dump出来的堆转储快照进行分析,分析出是内存泄漏还是内存溢出。

如果是内存泄漏(程序申请内存后,无法正常释放),查看泄露对象到GC Roots的引用链,就能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾回收器无法自动回收他们的。

如果是内存溢出(申请内存时没有足够的空间),不存在泄露,就是内存中的对象还必须存活着,那就检查虚拟机的堆参数(-Xmx与-Xms),与及其物理内存对比看是否还可以调大。

二、虚拟机栈和本地方法栈溢出

-Xss设置栈大小

为每个线程的栈分配的内存越大,反而越容易产生内存溢出,因为每个线程分配到的栈容量越大,可以建立的线程数量就越少,建立线程时就越容易把剩下的内存耗尽。

如果建立线程导致内存溢出,在不能减少线程数或更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

三、方法区和运行时常量池溢出

JDK1.6以前运行时常量池是在永代区

String.intern()是一个Native方法:作用:如果字符串常量池中已经包含了一个等于此String队形的字符串,则返回代表池中这个字符串的String对象,否则,将此String对象包含的字符串添加到常量池,并且返回此String对象的引用。

String str1 = new StringBuilder("计算机”).append("软件").toString();

 System.out.println(str1.intern() == str1);

String str2 = new StringBuilder("ja”).append("va").toString();

 System.out.println(str2.intern() == str2);

 JDK1.6是两个false:,JDK1.7是一个true一个false:intern()会把首次遇到的字符串实例复制到常量池,返回的是常量池中的引用,而StringBuilder的实例是在java堆上,。而JDK1.7的intern()不会复制实例,知识在常量池中记录首次出现的实例引用。

方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。这一区域的测试基本的思路就是填满直到溢出,比如CGLib对类的动态增强还有JVM的动态语言Groovy。

四、本机直接内存溢出

DirectMemory容量可通过-XX:MaxDirectMemorySize指定,不指定则与java堆最大值(-Xmx指定)一样

有DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中看不到明显的异常。 

posted @ 2017-10-19 21:06  赛跑的流氓龟  阅读(192)  评论(0编辑  收藏  举报