`
paladin1988
  • 浏览: 318840 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

01JVM内存区域结构

 
阅读更多

 

    Java虚拟机管理的内存区域包括:堆(Heap)栈(Stack)(本地方法栈和虚拟机栈)方法区程序计数器

 

 

 

程序计数器

 

    程序计数器:当前线程执行的字节码行号指示器,字节码解释器通过改变计数器的值来选取下一条需要执行的字节码指令。每个线程都需要一个程序计数器,各条线程之间的程序计数器互不影响。程序计数器是线程私有的。

 

如果线程正在执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行Native方法,这个计数器值为空(undefined)。此内存区域是唯一一个在Java虚拟机中没有规定任何OutOfMemoryError情况的区域。

 

 

栈(本地方法栈和虚拟机栈)

 

本地方法栈:执行Java虚拟机的本地方法(Native Method).

本地方法栈也会抛出StackOverFlowError和OutOfMemoryError异常。

 

虚拟机栈:它是线程私有的,生命周期和线程相同。它描述了Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个帧栈用于方法调用局部变量表,操作栈,动态链接,方法出口等信息。每一个方法的调用过程就是一个栈帧在虚拟机栈中从入栈到出栈的过程。

保存Java方法运行过程中的帧栈信息,包括方法调用的局部变量表,操作栈,动态链接以及方法入口等信息。

局部变量表存放了编译器可知的各种基本数据类型(boolean, byte, char, short, int, float, long, double)、对象引用。局部变量表所需内存空间在编译期完成分配,在方法运行期间不会改变局部变量表的大小。

Java虚拟机规范中对Java虚拟机栈规定了两中异常:
(1)如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;
(2)如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时将抛出OutOfMemoryError异常。

 

 

Java堆(Heap)

 

Java Heap是Java虚拟机所管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建,存放对象的实例。Java堆是垃圾收集器管理的主要区域。(GC Garbage Collected Heap)
Java堆分为新生代和老生代,再细分可以分为Eden空间, From Survivor空间, To Survivor空间。

Java堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可。
主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法在扩展,将会派出OutOfMemoryError异常。

 

 

 

方法区(Method Area, 永久带)

 

方法区是多个线程共享内存的区域,用于存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。垃圾收集行为很少在这个区域出现,该区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
当方法区无法满足内存分配需求时,讲抛出OutOfMemoryError异常。
方法区在HotSpot中也称为永久代(Permanent Generation)

运行时常量池(Runtime Constant Pool):运行时常量是方法区的一部分,常量池用来存放编译期生成的各种字面量和符号引用,它们将会在类加载后存放到方法去的运行时常量池中。
运行期间也可以将新常量放入池中。这种特性被开发人员用的比较多的就是String类的intern()方法。
当常量池无法再申请到内存时抛出OutOfMemoryError异常。

直接内存(Direct Memory):本地直接内存不会受到Java堆的限制,但是会出现OutOfMemoryError的错误。

 

 

=====================================================================

 

对象访问

 

Object obj = new Object();

 

Object obj将会反映到Java栈的本地变量中,作为一个reference类型数据出现。
而new Object()将会反映到Java堆中,形成一块存储了Object类型所有势力数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定的。

主流的对方访问方式有两种:使用句柄直接指针


(1)如果使用句柄方式访问,Java堆中将会划分出一块内存来作为句柄池,reference中存储的对象就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。



 


(2)直接指针方式访问,reference中直接存储的就是对象地址。



 

两者的区别:
1、使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,而对象被移动时值会改变句柄中的实例数据指针,而reference本身不需要被改变。
2、使用直接指针最大的好处是速度更快,它节省了一次指针定位的时间开销。

Sun Hotspot使用第二种方式进行对象访问。

 

 

 

=========================================================================

 

实战:OutOfMemoryError异常

 


在JVM规范中,除了程序计数器外,虚拟机内存的其他几个运行时区域都会发生OutofMemoryError异常(OOM)。

 

 

Java堆溢出

 

Java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆的容量限制后产生内存溢出异常。

不可扩展:将堆的最小值-Xms参数和最大值-Xmx参数设置为一样即可避免对自动扩展;
通过参数-XX:+HeapDumpOnOutOfMemoryError可让虚拟机在出现内存溢出时候Dump出当前的内存堆转储快照以便分析。

Java堆内存的异常是最常见的异常情况。
java.lang.OutOfMemoryError,提示Java heap space

解决方法:
(1)确认内存中的对象是否有存在的必要,分清楚是内存泄露(Memory Leak)还是内存溢出(Memory Overflow);
(2)如果是内存泄露,通过工具查看泄露对象到GC Roots的引用链。找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收他们。
(3)如果内存溢出,就应当检查虚拟机的堆参数(-Xms和-Xmx),与机器物理内存对比看是否还可以调大。
从代码上检查是否存在某些对象生命周期过长,持有状态时间过长的情况,尝试减少程序运行期间的内存消耗。

内存泄露主要是由于垃圾对象没有回收,而内存溢出主要是由于对象声明周期过长,消耗内存过大,虚拟机需要的堆内存大于可用内存。

 

 

 

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

 

在HotSpot虚拟机中不区分虚拟机栈和本地方法栈,所以对于HotSpot来说,-Xoss参数(设置本地方法栈大小)虽然存在,但是实际上是无效的。栈容量只由-Xss参数设定。

关于虚拟机栈和本地方法栈,JVM规范中描述了2种异常:
(1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常;
(2)如果虚拟机在扩展栈时无法申请足够的内存空间,则抛出OutOfMemoryError异常。

 

 

 

方法区溢出

 

方法区用于存放Class的相关信息,如类名,修饰符,常量池,字段描述和方法描述等。

对这个区域的测试,基本思路是运行时产生大量的类去填满方法区,直到溢出。
借助CGLib直接操作字节码运行时,生成了大量的动态类。SSH框架中对类进行增强时,都会使用到CGLib等字节码增强技术,增强的类越多,就需要越大的方法去来保证动态生成的Class可以加载入内存。

 

运行时常量溢出

向运行时常量池中添加内容,最简单的方法使用 String.intern()这个本地方法。
该方法的作用:如果池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;
否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
由于常量池分配在方法区内,通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。

运行时候常量池溢出,在OutOfMemoryError后面跟随的提示信息是“PermGen space”,说明运行时常量池属于方法去的一部分。

 

 

程序计数器,虚拟机栈,本地方法栈这三个区域和线程的生命周期相同
每一个栈帧中分配多少内存基本上在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具有确定性,不需要过滤考虑回收问题,因为方法结束或线程结束,内存自然就被回收了。

而Java堆和方法区的内存则不一样,一个接口中的多个实现可能需要的内存不一样,这部分的内存分配和回收是动态的。

 

 

==========================================================================

 

小结:

 

1、JVM的内存分配结构图;

2、堆,栈(本地方法栈和虚拟机栈),方法区,程序计数器分别存储的内容信息

3、内存溢出和内存泄露的区别?如何处理内存溢出与内存泄露

4、堆,栈,方法区,程序计数器可能出现的异常处理

 

 

 

 

 

  • 大小: 67.5 KB
  • 大小: 84.7 KB
  • 大小: 88.2 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics