欢迎光临我的博客[http://poetize.cn],前端使用Vue2,聊天室使用Vue3,后台使用Spring Boot
jvm虚拟内存分布
程序计数器(PC寄存器)(线程私有):
每个线程启动的时候,都会创建一个PC(Program Counter,程序计数器)寄存器。
PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,
也可以是在方法区中相对应于该方法起始指令的偏移量。
本地方法栈(线程私有):
jvm调用操作系统native方法所使用的栈。
虚拟机栈(线程私有):
栈帧(栈的单位):每个方法执行,都会创建一个栈帧,保存到栈的顶部,方法执行完毕后出栈。
栈帧用于存储局部变量表,操作数栈,动态链接,方法出口
局部变量表:存放编译器可知的各种基本数据类型,引用类型的引用,返回地址等。
Java栈的区域很小,特点是存取速度很快,
所以在stack中存放的都是快速执行的任务,基本数据类型的数据,和对象的引用(reference)
每个线程包含一个栈区,栈分为3个部分:基本数据类型的变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区(静态区)(所有线程共享):
存储加载的:①类(class),②静态变量(static变量),③静态方法,④常量,⑤成员方法
类信息包含类的版本、字段、方法、接口等信息
运行时常量池(包含了类的运行时常量和静态方法等Class常量池的数据)
虚拟机堆(所有线程共享):
唯一目的就是存放对象实例(与引用是两个概念),也是垃圾回收器主要管理的地方,故又称GC堆。
JVM堆内存的划分
在JDK7以及其前期的JDK版本号中:
堆内存通常被分为三块区域:新生代内存(young generation)、老生代(old generation)、永生代(Permanent Generation for VM Matedata)
Java8:
把存放元数据中的永生代内存从堆内存中移到了叫做“Metaspace”的本地内存(Native memory)。
Permanent:
即持久代(Permanent Generation):
主要存放的是Java类定义信息,与垃圾收集器要收集的Java对象关系不大。
持久代对垃圾回收没有显著影响。
但是有些应用可能动态生成或调用一些Class,例如Hibernate CGLib等。
在这种时候往往需要设置一个比较大的持久代空间来存放这些运行过程中动态增加的类型。
Heap = Old + NEW = Eden, from, to
Old 即 年老代(Old Generation)
New 即 年轻代(Young Generation)。
堆的大小可通过参数 -Xms(堆的初始容量)、-Xmx(堆的最大容量) 来指定
默认的,Edem : from : to = 8 : 1 : 1 (可以通过参数 -XX:SurvivorRatio 来设定)。
年轻代:
所有新生成的对象首先都是放在年轻代。
年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
年轻代一般分3个区,Eden区,Survivor区(from和to)。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
针对年轻代的垃圾回收即Young GC。
年老代:
在年轻代中经历了N次(可配置)垃圾回收后仍然存活的对象,就会被复制到年老代中。
因此,可以认为年老代中存放的都是一些生命周期较长的对象。
针对年老代的垃圾回收即Full GC。
Young GC回收过程(复制算法)
Eden –> Survivor Space 以及 From Survivor Space 与 To Survivor Space 之间实行 Copying 算法。
1,当Eden区满的时候,会触发第一次young gc,把还活着的对象拷贝到Survivor From区。
Survivor From区放不下,就将剩下的对象放老年代。
2,当再次触发young gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收。
经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。
如果一次回收中,Survivor+Eden中存活下来的内存超过了10%,则需要将一部分对象分配到老年代
3,当后续Eden又发生young gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
4,部分对象会在From和To区域中复制来复制去,如此交换15次
(默认是15次,表示最大值,但不一定15此,可以通过参数 -XX:MaxTenuringThreshold 来设定),
最终如果还是存活,就存入到老年代。
但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) ,则是直接进入到老年代。
优点:不会产生内存碎片,效率高。
缺点:需要双倍空间。
Full GC(标记-清除算法)
1. 标记:
从引用根节点开始扫描,标记所有被引用的对象
2. 清除
扫描整个堆内存空间,回收未被标记的对象。
优点:不需要额外空间
缺点:
两次扫描,耗时严重;产生内存碎片。
以后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。
特点:
Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长,一般是 Minor GC 的 10倍以上。
标记整理算法(Mark-Compact):
在原有的标记-清除算法的基础上,提出了优化方案。
标记到的可用对象整体向一侧移动,然后直接清除掉可用对象边界以外的内存。
这样既解决了内存碎片的问题。又不需要原有的空间换时间的硬件浪费。
标记整理算法的缺点:
标记整理算法由于需要不断的移动对象到另外一侧,而这种不断的移动其实是非常不适合杂而多的小内存对象的。
每次的移动和计算都是非常复杂的过程。
因此在使用场景上,就注定限制了标记整理算法的使用不太适合频繁创建和回收对象的内存中。