一, 概述:
Java 跟 C++ 不同, 在内存管理区域 C++ 程序员拥有着最高权力, 但是正是因为如此, 所以 C++ 程序员要照顾这个对象的生老病死, 从创建到消亡都是由程序员决定的.
但是 Java 程序员在虚拟机的自动内存机制的帮助下, 不需要去写对应的 delete/free 代码, 但是我们还是应该去了解该机制, 不然在 DEBUG 时候将是一条很难的路.
二, 运行时数据区域:
程序计数器 (Program Counter Register): 是一块较小的内存空间, 它可以看作是当前程序锁执行的字节码的行号指示器, 在并发执行中, 每个线程都有自己的程序计数器, 各线程之间计数器互不影响, 独立存储,, 我们称这一类内存区域为 "线程私有" 的内存. 计数器记录正在执行的虚拟机字节码指令的地址, 如果正在执行本地(Native) 方法, 则计数器值为空(Undefined).
Java 虚拟机栈 (Java Virtual Machine Stacks):java 虚拟机栈跟计数器一样也是线程私有的, 生命周期与线程相同. 虚拟机栈描述的是 Java 方法执行的内存模型: 每个方法执行的同时会创建一个栈帧(Stack Frame) 用于储存局部变表, 操作数栈, 常量池引用, 动态链接方法出口等信息, 每一个方法从调用到执行完成的过程, 就对应着一个栈帧在虚拟机中从入栈到出栈的过程.
在 Java 虚拟机规范中, 对这个区域规定了两种一场状况: 请求的栈深度大于虚拟机所允许的深度, 将抛出 StackOverflowError 异常, 如果虚拟机在动态扩展时无法申请到足够的内存, 就会抛出 OutOfMemoryError 异常.
可以通过 - Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存的大小:
java -Xss512M HackTheJava
本地方法栈 (Native Method Stack): 跟虚拟机栈相似, 区别在于虚拟机栈执行 JAVA 方法(也即是字节码服务) 服务, 二本地方法栈顾名思义就是虚拟机使用到的本地 (Native) 方法服务.
Java 堆(Java Heap): 是内存区域最大的一块, 是线程共享的区域, 唯一的目的是用来存放对象实例, 正因为如此, 这里也是垃圾收集器管理的主要区域, 也叫做 "GC 堆"(Garbage Collected Heap)~~(垃圾堆, hhhhhh~)
一般把 JAVA 堆分成:
新生代
老年代
堆不需要连续内存, 并且可以动态增加其内存, 增加失败会抛出 OutOfMemoryError 异常.
可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小, 第一个参数设置初始值, 第二个参数设置最大值.
java -Xms1M -Xmx2M HackTheJava
方法区(Method Area): 用于存放已被加载的类信息, 常量, 静态变量, 即时编译器编译后的代码等数据. 和堆一样不需要连续的内存, 并且可以动态扩展, 动态扩展失败一样会抛出 OutOfMemoryError 异常.
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载, 但是一般比较难实现.
HotSpot 虚拟机把它当成永久代来进行垃圾回收. 但很难确定永久代的大小, 因为它受到很多因素影响, 并且每次 Full GC 之后永久代的大小都会改变, 所以经常会抛出 OutOfMemoryError 异常.
为了更容易管理方法区, 从 JDK 1.8 开始, 移除永久代, 并把方法区移至元空间, 它位于本地内存中, 而不是虚拟机内存中.
运行时常量池(Runtimes Constant Pool): 运行时常量池是方法区的一部分.
Class 文件中的常量池 (编译器生成的字面量和符号引用) 会在类加载后被放入这个区域. 除了在编译期生成的常量, 还允许动态生成, 例如 String 类的 intern().
直接内存(Direct Memory): 在 JDK 1.4 中新引入了 NIO 类, 它可以使用 Native 函数库直接分配堆外内存, 然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作. 这样能在一些场景中显著提高性能, 因为避免了在堆内存和堆外内存来回拷贝数据.
部分图片参考了 https://github.com/CyC2018/CS-Notes 以及网上的图片, 侵删
来源: http://www.bubuko.com/infodetail-3012180.html