`
海浪儿
  • 浏览: 271661 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

jvm学习笔记1--内存区域与内存溢出

    博客分类:
  • jvm
阅读更多

jvm学习笔记1--内存区域与内存溢出

  1. 运行时数据区域组成 

 

1.1   程序计数器

  • 当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令。
  • 为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,因此属于“线程私有”的内存。
  • 执行 java方法,则记录的是虚拟机字节码的地址;如果执行的是 native方法,则值为空( Undefined
  • 异常:唯一一个在 Java虚拟机规范中没有规定任何 OutOfMemoryError情况的区域。

1.2   Java虚拟机栈( java virtual machine stacks

  • Java虚拟机栈是线程私有的,生命周期与线程相同。
  • 栈帧( stack frame):每个方法被执行的时候会创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  • 局部变量表:存放编译器可知的各种基本数据类型、对象引用(根据不同的虚拟机实现,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄)、 returnAddress类型(指向一条字节码指令的地址)。局部变量表的空间在编译期是完全确定的,所以在编译期间完成分配,在运行期间不会再改变大小。
  • 异常:

1)  如果线程请求的栈深度大于虚拟机所允许的深度,抛出 StackOverflowError

2) 如果虚拟机栈可以动态扩展,当扩展时无法申请到足够内存,则抛出 OutOfMemoryError异常。

 

1.3   本地方法栈

  • 为虚拟机使用到的 native方法服务
  • 异常:会抛出 StackOverflowError OutOfMemoryError

1.4   Java

  • 所有线程共享,在虚拟机启动时创建。唯一目的就是存放对象实例。
  • 是垃圾收集器管理的主要区域,也被称作 GC
  • 异常:如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出 OutOfMemoryError异常。

1.5   方法区

  • 所有线程共享。存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 垃圾收集行为:内存回收目标主要是针对常量池的回收和对类型的卸载。
  • 运行时常量池:

1) 属于方法区的一部分。

2) 存放内容: Class文件中的常量池(存放编译期生成的各种字面量和符号引用);翻译出来的直接引用;运行期间产生的新的常量(譬如 String类的 intern()方法)

  • 异常: OutOfMemoryError

1.6   直接内存:

  • 不属于虚拟机运行时数据区
  • NIO,可用使用 Native函数库直接分配堆外内存,然后通过一个存储在 java堆里面的 DirectByteBuffer对象作为这块内存的引用进行操作,避免了在 Java堆和 Native堆中来回复制数据。
  • 异常:不受 java 堆大小的限制,但受本机总内存的大小及处理器寻址空间的限制,会抛出 OutOfMemoryError

2. 对象访问

2.1 Object  obj  =  new Object

  • Object obj:作为一个reference类型数据存储在栈帧的本地变量表中;
  • new Object():作为一个Object类型的实例数据存在在java堆中;同时,还必须包含能找到Object类型数据(如对象类型、父类、实现的接口、方法等)的地址信息;
  • Object类型数据(如对象类型、父类、实现的接口、方法等)存储在方法区中;

2.2 两种访问对象的方式:句柄、直接指针

  • 句柄访问方式:

Java堆中会划分出一块内存来作为句柄池;栈帧中存储的reference值就是对象的句柄地址;句柄中包含了对象实例数据和类型数据各自的具体地址信息

优点:对象被移动时,只需要改变句柄中的实例数据指针,而reference本身不需要被修改。

  • 直接指针访问方式:

Java堆对象的布局必须考虑如何放置类型数据的相关信息;栈帧中存储的reference值就是对象地址。

优点:速度更快,节省了一次指针定位的时间开销。Sun HotSpot使用该方式。

 

 

3. 内存溢出

3.1   Java堆溢出

  • Vm args:  -Xms40M -Xmx40M -XX:+HeapDumpOnOutOfMemoryError
  • 异常堆栈信息:Java.lang.OutOfMemoryError: Java heap space
  • 方案:

1)通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转储快照进行分析,确认是内存泄漏还是内存溢出

2) 如果是内存泄漏,则查看泄漏对象到GC Roots的引用链,得知泄漏对象通过怎样的路径与GC Roots相关联导致无法回收。

3)如果是内存溢出(即对象确实都还必须存活着),则检查堆参数(-Xmx-Xms)与机器物理内存对比看是否可以调大;从代码上检查是否存在某些对象生命周期过长的情况,尝试优化减少程序运行期的内存消耗。

 

3.2   虚拟机栈溢出

  • Vm args-Xss2M
  •  异常堆栈信息:

1)StackOverflowError

Exception in thread “main” java.lang.StackOverflowError

说明:单线程情况下,不管是由于栈帧太大,还是由于虚拟机栈容量太小,当内存无法分配的时候,都抛出StackOverflowError

2)OutOfMemoryError

Exception in thread “main” java.lang.OutOfMemoryError:unable to create new native thread

多线程情况下,通过不断地建立线程的方式可以产生该异常,而且,为每个线程的栈分配的内存越大,越容易产生该异常。

  • 方案

1)  StackOverflowError

有错误堆栈可以阅读,容易找到问题所在。而且,如果使用虚拟机默认参数,栈深度对于正常的方法调用是完全够用的。

2)  OutOfMemoryError

由于虚拟机提供了参数来控制Java堆和方法区这两部分内存的最大值,而程序计数器消耗的内存很小可以忽略掉,因此用本机内存扣除掉这两部分内存后,剩下的内存就由虚拟机栈和本地方法栈瓜分了。所以对于建立多个线程导致的内存溢出,有两个措施解决:

2.1 减少最大堆内存

2.2  减少栈容量(是为了能够创建更多的线程)

 

3.3   运行时常量池溢出

  • Vm args: -XX:PermSize=10M -XX:MaxPermSize=10M
  • 可能原因:运行时产生了大量的新的常量(譬如String类的intern()方法)
  • 异常堆栈信息: Exception in thread “main” java.lang.OutOfMemoryError:PermGen space   

3.4   方法区溢出

  • Vm args: -XX:PermSize=10M -XX:MaxPermSize=10M
  • 可能原因:运行时产生了大量的类,导致在方法区存储了大量的类型数据信息(譬如CGLIB动态生成子类、动态产生JSPOSGI应用(即使是同一个类文件,不同的classloader也会视为不同的类))
  • 异常堆栈信息:Exception in thread “main” java.lang.OutOfMemoryError:PermGen space

3.5   本机直接内存溢出

  • Vm args: -XX:MaxDirectMemorySize=10M(如果不指定该参数,则默认与java堆的最大值一样)
  • 异常堆栈信息:Exception in thread “main” java.lang.OutOfMemoryError

注:本笔记主要参考:深入理解Java虚拟机--Jvm高级特性与最佳实践一书及网络资料

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics