一,JVM内存区域的划分
1.程序计数器(线程自有,存当前方法的指令地址)
在 JVM 规范中,每个线程都有它自己的程序计数器,并且任何时间一个线程都只有一个方法在执行。程序计数器会存储当前线程正在执行的 Java 方法的 JVM 指令地址,或者,如果是在执行本地方法(比如jvm依赖的其他程序),则是未指定值(undefined)。
2.Java 虚拟机栈(线程自有)
早期也叫 Java 栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的 Java 方法调用。
在一个时间点,对应的只会有一个活动的栈帧,通常叫作当前帧,方法所在的类叫作当前类。如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,成为新的当前帧,一直到它返回结果或者执行结束。JVM 直接对 Java 栈的操作只有两个,就是对栈帧的压栈和出栈。
栈帧中存储着局部变量表、操作数栈、动态链接、方法正常退出或者异常退出的定义等。
3.堆(线程共享)
它是 Java 内存管理的核心区域,用来放置 Java 对象实例,几乎所有创建的 Java 对象实例都是被直接分配在堆上。堆被所有的线程共享,在虚拟机启动时,我们指定的“Xmx”之类参数就是用来指定最大堆空间等指标。
理所当然,堆也是垃圾收集器重点照顾的区域,所以堆内空间还会被不同的垃圾收集器进行进一步的细分,最有名的就是新生代、老年代的划分。
4.方法区/元数据区(线程共享)
这也是所有线程共享的一块内存区域,用于存储元(Meta)数据,例如类结构信息,以及对应的运行时常量池、字段、方法代码等。
由于早期的 Hotspot JVM 实现,很多人习惯于将方法区称为永久代(Permanent Generation)。Oracle JDK 8 中将永久代移除,同时增加了元数据区。
运行时常量池
这是方法区的一部分。Java 的常量池可以存放各种常量信息,不管是编译期生成的各种字面量,还是需要在运行时决定的符号引用,所以它比一般语言的符号表存储的信息更加宽泛。
5.本地方法栈(线程自有)
它和 Java 虚拟机栈是非常相似的,支持对本地方法的调用,也是每个线程都会创建一个。在 Oracle Hotspot JVM 中,本地方法栈和 Java 虚拟机栈是在同一块儿区域,这完全取决于技术实现的决定,并未在规范中强制。
JVM学习笔记二,OutOfMemoryError可能发生的区域
在抛出 OutOfMemoryError 之前,通常垃圾收集器会被触发,尽其所能去清理出空间。jvm没有空闲内存,并且垃圾收集器也无法提供更多内存,就会抛出OutOfMemoryError异常。
当然,也不是在任何情况下垃圾收集器都会被触发的,比如,我们去分配一个超大对象,类似一个超大数组超过堆的最大值,JVM 可以判断出垃圾收集并不能解决这个问题,所以直接抛出 OutOfMemoryError。
除了程序计数器,其他区域都有可能会因为可能的空间不足发生 OutOfMemoryError
可能不足的区域:
1.堆内存
抛出的错误信息是“java.lang.OutOfMemoryError:Java heap space”,原因可能千奇百怪,例如,可能存在内存泄漏问题;也很有可能就是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显式指定 JVM 堆大小或者指定数值偏小;或者出现 JVM 处理引用不及时,导致堆积起来,内存无法释放等。
2.Java 虚拟机栈和本地方法栈
如果我们写一段程序不断的进行递归调用,而且没有退出条件,就会导致不断地进行压栈。类似这种情况,JVM 实际会抛出 StackOverFlowError;如果 JVM 试图去扩展栈空间的的时候失败,则会抛出 OutOfMemoryError。
3.老版本的永久代
对于老版本的 Oracle JDK,因为永久代的大小是有限的,并且 JVM 对永久代垃圾回收(如,常量池回收、卸载不再需要的类型)非常不积极,所以当我们不断添加新类型的时候,永久代出现 OutOfMemoryError 也非常多见,尤其是在运行时存在大量动态类型生成的场合;类似 Intern 字符串缓存占用太多空间,也会导致 OOM 问题。对应的异常信息,会标记出来和永久代相关:“java.lang.OutOfMemoryError: PermGen space”。
4.新版本的元数据区
新版本引入元数据区后方法区内存不再那么窘迫,所以相应的 OOM 有所改观,出现 OOM,异常信息则变成了:“java.lang.OutOfMemoryError: Metaspace”。
5.直接内存
略。
三,案例分析
试图分配一个 100M bytes 大数组的时候发生了 OOM,但是 GC 日志显示,明明堆上还有远不止 100M 的空间,可能问题的原因是什么?
1.新生代与老生代的内存一共超过100M,但却都不足100M。
2.给数组分配的必须是连续地址才行,而显示的是总的地址。
3.由于大数组一般直接进入老年代(会跳过对对象的年龄的判断),所以,可以认为老年代中没有连续大于 100M 的空间。
最后推荐一本书《深入理解 Java 虚拟机》
网友评论