相关文章
JVM运行时会把它所管理的内存分为若干个不同的数据区域。方法区、堆、虚拟机栈、本地方法栈、程序计数器。其中虚拟机栈、本地方法栈和程序计数器是线程私有的,其他是所有线程共享的。
程序计数器
- 当前线程所执行的字节码的行号指示器。如果JVM是解释执行的,字节码解释器就会通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 线程私有
- 如果是Java方法,此计数器存的是正在执行的虚拟机字节码指令的地址;如果是本地方法,此计数器的值为空。
- 此区域是唯一在JVM规范中没有规定任何OutOfMemoryError的区域。
虚拟机栈
- 线程私有
- 其生命周期与线程相同
- 虚拟机栈描述的是Java方法执行的内存模型:每个方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就是栈帧在虚拟机栈中从入栈到出栈的过程。
- 局部变量表:其大小在编译期就确定了大小,在运行时不会改变。
- 基本数据类型,单位slot,double和long占用2个slot。
- 对象引用
- returnAddress:指向一条字节码指令的地址(return指令)
- 抛出StackOverflowError:受-Xss参数控制(每个线程的栈内存),默认1M。也受thread的stackSize的影响。
- 抛出OutOfMemoryError:虚拟机栈如果可以动态扩展,在扩展时无法申请足够在内存,就会抛出OutOfMemoryError异常。也就是说受到本机内存的限制。
本地方法栈
- 本地方法栈为虚拟机使用的本地方法服务。JVM规范对于本地方法栈中方法使用的语言、数据结构没有强制规定,有具体的JVM自由实现。HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一!!
- 抛出StackOverflowError和OutOfMemoryError。
Java堆
- 总的来说是线程共有的,TLAB本地线程分配缓冲是线程私有的。
- JVM管理的最大一块内存,用来存放对象。几乎所有的对象都在堆上进行分配。但是随着JIT技术的发展和逃逸分析技术、栈上分配、标量替换等技术的出现,对象就不一定非得在堆上进行分配了。
- 堆是垃圾收集器管理的主要区域。
- 如果是分代收集算法,堆会被分成几个空间:
- 新生代:Eden区、From Survivor区、To Survivor区
- 老年代
- 注意java堆可以在物理上不连续,只要逻辑上连续即可。
- 通过-Xms(初始大小)和-Xmx(最大大小)控制。
- 如果堆中没有空间来分配,会抛出OOM。
方法区
- 方法区:用于存储已经被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。
- 是线程共享的。
- 对于这个区的垃圾回收主要针对常量池的回收和对类的卸载。
- 内存不够时会抛出OOM
- 某些版本的JVM,会受-XX:MaxPermSize参数的控制。
- 在JDK1.7中把字符串常量池(string pool)从这个区中移出到堆中了。
运行时常量池 Runtime Constant Pool
- 运行时常量池是方法区的一部分!
- 源文件在编译成class文件后,会有一个常量池,用于存放编译期生成的各种字面量和符号引用。这部分内容在类加载后会进入方法区的运行时常量池!符号引用在解析后会变成直接引用,这个直接引用就放在运行时常量池中。
- 运行时常量池与Class文件的常量池有个重要区别是,运行时常量池具备动态性。也就是说在运行期间也可以把新的常量放入池中,比如String的intern()方法。这是个native方法。
直接内存
- 直接内存 direct memory:并不是虚拟机运行时数据区的一部分,也不是JVM规范中定义的内存区域。但是这部分内存也会频繁地被使用,也可能导致OOM
- JDK1.4加入了NIO,引入了一种基于通道和缓冲区的IO方式,它可以使用本地函数库直接分配堆外内存,然后通过Java堆中的DirectByteBuffer对象作为对这块内存的引用进行操作。这样可以避免数据来Java堆和Native堆中来回地复制。
- 此块区域的内存不收Java堆大小的控制,但是收到本机总内存的(RAM、SWAP区以及分页文件)的大小和处理器的寻址空间的限制。
关于Java对象的创建和内存布局,可以看我的这篇总结。
欢迎关注个人博客公众号: