程序计数器
线程私有
因为物理 cpu 并不多, 所以 jvm 是对 java 里面的线程进行不停的切换执行, 因为切换的执行速度太快, 所以我们看到是并发执行. 所以 jvm 在切换线程执行后, 如果要切换回原来的线程, 它需要记住这个线程的执行位置, 下一条指令是什么. 所以每一个线程都有一个独立的程序计数器, 它是线程私有的.
数据内容
程序计数器保存了每个对象的引用数量, 但是也不仅仅是对象的引用, 它保存了一个线程中一系列需要执行的字节码指令的内存地址, 包括循环, 异常等
native 方法
如果当前正在执行的是 native 方法, 那么它在程序计数器里面的值是空 (undefined).
java 栈
线程私有
java 栈保存的是执行每一个方法的内容, 所以每执行一个方法, 都会创建一个栈帧 (StackFrame), 保存局部变量, 操作数栈, 动态链接, 方法的进出信息等, 直到一个方法调用完成, 就意味着一个栈帧从进去到出来的过程, 所以它也是线程私有的.
数据内容
java 栈帧中, 保存了当前局部的基本数据类型 (boolean,byte,char,short,int,float,long,double), 以及对象引用.
对象引用, 这里指的是定义的那些对象, 但是值得注意的是, 这里保存的是引用, 而不是具体的内容, 当我们 new 一个对象时, jvm 会把创建的引用放在栈里面, 但是对象本身, 是存在堆里面的, 而引用只是保存了对象在堆里面的内存地址, 这是因为栈内存很小, 但是栈读取数据快, 所以存储了引用, 而我们开辟出来的对象, 或者申请的内存是放在堆里面的.
局部变量所需要的内存, 在一开始就是确定的, jvm 会按照变量类型计算. 因为当进入一个栈帧时, 所需要的内存是确定的, 直到出栈, 这里面的内存不会发生任何变化.
栈异常
jvm 中对于栈规则了两种异常.
当 java 类中的方法进入次数太多时, 会导致栈的层次越来越深, 如果请求的栈深度, 超出了 jvm 虚拟机所允许的深度, 就会抛出 StackOverflowError 异常.(当前绝大部分虚拟机都是可以动态调整栈深度的, 所以一般不会出现这个问题, 但是也不排除, 因为 jvm 规范中也允许固定长度的栈深度)
另一方面, 如果扩展栈深度时, 无法申请到足够的内存, 就会抛出 OutOfMemoryError 异常.
所以当我们遍历文件夹的时候, 最好不要用递归, 因为可能出现栈溢出的异常.
本地方法栈
本地方法栈所起的作用和 java 栈的作用几乎一致, 只不过本地方法栈中, 保存的是 native 方法的栈信息, 但是虚拟机规范中, 对于 native 方法的实现语言, 实现类型, 数据结构并没有明确规定, 各种虚拟机可以自由实现它, 比如 Sun HotSpot 虚拟机就把 java 栈和本地方法栈合二为一了.
本地方法栈也有着 StackOverflowError 和 OutOfMemoryError
java 堆
堆是所有线程共享的, 它是 jvm 管理内存的最大的一块区域, 也是 java 程序员所能操控的内存区域.
数据内容
java 程序员所能操控的内存, 虽然对于程序员来说没有感知, 但实际上全部是在堆里面操作, 比如我们 new 出来的对象, 以及数组, 其实都是存放在堆内存里面的.
垃圾回收
java 堆是 gc 回收内存的主要区域, 因为现在的内存回收算法基本都是采用分代算法, 所以还可以分为新生代和老生代, 这样的分配是为了更快的找出需要回收的内存, 提高 gc 效率. 甚至还可以更往细分 Eden,From Survivor,To Survivor 等.
空间大小
java 堆里面的内存可以是物理上不连续的内存, 只要是逻辑上连续就可以, 一般主流虚拟机, 都是可以在启动的时候, 根据启动参数指定内存大小 (-Xms -Xmx), 如果在使用内存时, jvm 无法再申请新的堆内存, 就会抛出 OutOfMemoryError 异常.
方法区
方法区是所有线程共享的区域, 方法区也叫永久代, 因为它永远不会被 gc 回收.
数据内容
用于存储虚拟机加载的类信息, 常量, 静态变量等数据, 这些数据是在类加载器加载时候完成的, 所以虽然说 new 出来的对象是存在堆里面的, 但是如果这个对象是常量, 那么在类加载器加载这个类的时候, 就会把这些静态变量存储到方法区里面去.
异常信息
同样的, 方法区的内存无法满足内存的根本需求时, 抛出 OutOfMemoryError 异常
堆外内存 (直接内存)
堆外内存是一块独立的内存, 值得注意的是, 它是由 java 程序员完全操纵的一个内存, 意味着, 程序员需要显式的申请内存, 以及手动释放内存, 因为它不由 gc 管理.
它的优点是因为直接操作内存, 在某些应用场景中, 可以避免内存的复制, 以及回收再创建, 可以提升内存的利用率.
它的缺点就是需要手动释放内存, 而不是交给 gc 来处理, 所以使用不当, 很容易抛出 OutOfMemoryError 异常.
堆外内存不受到堆内存的限制, 也就是不受到 - Xmx 的限制, 但是还是受到物理内存的限制, 如果超出物理内存, 就就会抛出 OutOfMemoryError
来源: https://www.cnblogs.com/zhuxiaojie/p/9251447.html