1. 运行时数据区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的区域

1.1 程序计数器
- 一块较小的内存空间
- 作用是当前现成所执行字节码的行号指示器
- 是一块私有内存区域,因为为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器
- 如果正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码的地址;如果正在执行的是Native方法,这个计数器为空
- 没有规定任何
OutOfMemoryError
情况的区域
1.2 Java虚拟机栈
- 线程私有,生命周期与线程相同
- 虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候创建一个线帧用于存储局部变量表,操作栈,动态链接,方法出口等信息,每一个方法被执行完,就意味着一个栈帧从入栈到出栈
- 通常说的栈内存就是Java虚拟机栈
- 局部变量表存放了编译器可知的各种基本数据类型,对象引用和returnAddress类型
- 规定了两种异常状况:1)如果线程请求的深度超过了虚拟机允许的栈深度,将抛出
StackOverflowError
异常 2)如果虚拟机可以扩展,而扩展时无法获得足够的内存,则会抛出OutOfMemoryError
异常
1.3 本地方法栈
- 与虚拟机栈类似,区别是本地方法栈是为虚拟机使用到的Native方法
- 会抛出
StackOverflowError
和OutOfMemoryError
异常
1.4 Java堆
- Java虚拟机所管理的内存中最大的一块
- 线程共享的一块内存区域,在虚拟机启动时创建
- 此内存区域的唯一目的是存放对象实例
- 垃圾收集管理的主要区域
- 可以处于物理上不连续的内存空间
- 目前主流虚拟机都是可以扩展的,如果堆中无法完成实例分配,堆也无法再扩展时,会抛出
OutOfMemoryError
异常
1.5 方法区
- 各个线层共享的区域
- 用于存放已被虚拟机加载的类信息、常量、静态变量等数据
- Java虚拟机对这个区域限制宽松,可以不需要连续的物理空间,选择不实现垃圾回收
-当方法区无法满足内存分配需要时,会抛出OutOfMemoryError
异常
1.6 运行时常量池
- 方法区的一部分
- Class文件中除了有类的版本,字段,方法,接口等信息的描述外,还有一项信息就是常量池,用于存放各种编译器生成的字面量和符号引用,这部分内容在类加载后存放到方法区的运行时常量池中
- 一般来说,除了保存class文件中描述的符号引用,还会把翻译出来的直接引用存放在运行时常量池中
- 具备动态性,并非预置入Class常量池的内容才能进入方法区运行时常量池
- 常量池无法申请到内存时,会抛出
OutOfMemoryError
异常
1.7 直接内存
- 并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但这部分区域经常被使用
- JDK1.4 中加入了NIO类,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的
DirectByteBuffer
对象作为这块内存的引用进行操作 - 服务器管理员在配置虚拟机参数时,如果忽略掉直接内存,可能会使得各个部分的内存大于实际物理内存的情况,导致
OutOfMemoryError
异常
2. 对象访问
- 最简单的访问也会涉及到Java栈,Java堆和方法区三个内存区域
Object object = new Object();
-
Object object
意味着在Java栈中生成一个reference
对象 -
new Object()
意味着在Java堆中形成了一块存储object实例的内存,存放对象实例数据 - 方法区中存储此对象的对象类型数据
对象访问方式
- 句柄池:
reference
存储句柄池地址,句柄池存储对象的实例数据和类型数据各自的具体地址信息
优点:稳定,对象被一定(垃圾回收)只改变句柄中实例数据的指针,reference
本身不会改变 - 直接指针:
reference
直接存储地址
优点:速度快
3. OutOfMemory异常
3.1 Java堆溢出
-
Java堆用于存储对象实例,并且保证GC ROOTS到对象之间有可达路径来避免垃圾回收机制回收这些对象,就会在对象数量达到最大堆的容量限制后产生内存溢出异常
jvmoptions
-server
-Xms128m
-Xmx512m
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-Djdk.http.auth.tunneling.disabledSchemes=""
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Dawt.useSystemAAFontSettings=lcd
-Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine
-Dsun.tools.attach.tmp.only=true
-
-Xms
代表堆的最小值-Xmx
代表堆的最大值 -
-XX:+HeapDumpOnOutOfMemoryError
可以dump出内存堆转储快照以便分析
解决手段
- 通过内存映像分析工具对dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要分析是出现了内存泄露还是内存溢出
- 如果是内存泄露,则用工具查看GCRoots的引用链,准确定位泄露代码的位置
- 如果是内存溢出,则检查是否存在某些对象是否生命周期过长、持有持续时间过长等情况
3.2 虚拟机栈和本地方法栈
public class demo {
int stackDepth = 1;
public void seekDepth() {
stackDepth ++;
this.seekDepth();
}
public static void main(String[] args) {
demo de = new demo();
try {
de.seekDepth();
} catch (Throwable e) {
System.out.println("-------------------" +de.stackDepth);
e.printStackTrace();
}
}
}
- 线帧太大,虚拟机栈容量太小都会造成
StackOverflowError
异常 - 多线程情况下,不断地建立线程会产生内存溢出错误。而且,给每个线的栈分配的内存越大,反而越容易产生内存溢出
原因:系统给每个进程的内存是有限制的,Java用虚拟机参数规定Java堆和方法区的内存最大值,剩余内存为操作系统限制 - Xmx - MaxPermSize,程序计数器消耗内存很小,可以忽略,剩下的内存由虚拟机栈和本地栈瓜分,每个线程分配的内存越多,能分配的线程就会越少,建立线程时就容易把内存耗尽
3.3 运行时常量池溢出
-
String.intern
:如果池中包含String,则返回这个String对象,否则添加此String,并返回String的引用 - 我们可以通过
-XX:PermSize
和-XX:PermSize
来限制方法区大小,从而限制其中的运行时常量池大小
3.4 方法区溢出
- 一个类如果要被垃圾回收机制回收,判定条件很苛刻,因此在经常动态生成大量Class的应用中,需要特别注意类的回收状况
3.5 本机直接内存溢出
- 可以通过
-XX:MaxDirectMemorySize
指定,如果不指定,则与Java堆的最大值相同
网友评论