文章目录
- 1. 程序计数器(Program Counter Register)
- 2. 虚拟机栈(VM Stack)
- 3. 本地方法栈(Native Method Stack)
- 4. Java堆(Java Heap)
- 5. 方法区(Method Area)
- 记忆集
- 卡表
- 小故事
JVM(Java虚拟机)的内存结构分为以下几个部分:
1. 程序计数器(Program Counter Register)
该部分是线程私有的存储区域,用于记录线程运行到的位置。当一个线程执行Java方法时,程序计数器记录的是正在执行的虚拟机字节码指令的地址;当执行Native方法时,这个计数器的值则为空(undefined)。
2. 虚拟机栈(VM Stack)
每个线程都会创建自己的虚拟机栈,用于存储方法运行时的局部变量表、操作数栈、动态链接、方法出口等信息。当一个方法被调用时,系统会为该方法创建一个栈帧(Stack Frame),并压入虚拟机栈中,当该方法执行完毕时,该栈帧就会被弹出。
3. 本地方法栈(Native Method Stack)
与虚拟机栈类似,本地方法栈是为虚拟机使用Native方法服务的。其作用是:为执行Native方法(不是Java语言编写的方法)提供支持。
4. Java堆(Java Heap)
Java堆是JVM所管理的内存中最大的一块,线程共享。是用于存放对象实例的内存区域,也就是说,几乎所有的对象实例都在堆中分配空间。Java堆是垃圾收集器进行垃圾收集的主要区域,因此又被称为GC堆。
Java堆是被分为新生代和老年代两个区域,其中新生代又被分为Eden空间和两个Survivor空间。大部分的对象都在新生代中生成,当新生代满了之后,会触发一次Minor GC,将新生代中垃圾对象进行清除。存活的对象将会被移动到Survivor空间中,当Survivor空间也满了之后,会触发一次复制算法的Minor GC,将存活的对象移动到另一个Survivor空间中。当一个对象经过多次的Minor GC后仍然存活下来,它就会被移动到老年代中。
5. 方法区(Method Area)
方法区也是线程共享的一块区域,用于存储已加载的类的结构信息、常量、静态变量等数据。相对于堆区的垃圾收集,方法区的垃圾收集频率比较小,一般只有在容器卸载时才会进行。
记忆集
在JVM垃圾回收过程中,需要记录哪些对象是存活的,哪些对象已经不再被使用。JVM通过记忆集(Remembered Set)和卡表(Card Table)进行记录。
记忆集是针对老年代中的对象进行记录,它用于标记某个区域中指向新生代的引用。当年老代进行回收时,只需要扫描记忆集中的引用,即可确定哪个对象是新生代存活的对象,哪些对象已经不再被引用。
卡表
卡表则是针对堆区进行记录,用于标记某个区域中一块内存区域的状态。当某个区域中的对象被修改时,卡表会将对应的内存区域标记为脏(Dirty),在进行老年代回收时,只需要扫描被标记为脏的内存区域即可,这样可以避免对整个老年代进行扫描,提升了垃圾回收的效率。
小故事
曾经有一个程序员小明,他开发了一个Java应用程序,但是遇到了内存泄漏的问题,导致程序经常崩溃。他找来了专业人士,经过调试,发现问题出在了JVM的记忆集和卡表上。
JVM的记忆集可以简单理解为一个大的集装箱,里面存储着程序需要使用的所有对象。当程序需要使用对象时,JVM会从记忆集里分配一块内存给对象使用,当对象不再使用时,JVM会回收这块内存。但是,如果对象的引用没有被及时释放,这块内存就不会被回收,就像集装箱里有一些没用的物品,挤占了其他物品的空间,导致程序崩溃。
而卡表可以理解为一个出入库记录表,记录某个对象的引用是否被卡住,如果被卡住了,就不能回收这个对象,否则就会出现空指针异常等问题。就像小明的公司,需要记录员工的进出时间,如果有员工还没回来,就不能关闭公司大门,否则会影响到其他员工的出行。
通过检查记忆集和卡表,小明和专业人士找到了内存泄漏的问题,释放了不必要的对象引用和修改了卡表记录,程序也终于恢复了正常运行。