jvm学习笔录

jvm内存模型

1程序计数器

一块较小的内存空间, 作用是当前线程所执行字节码的行号指示器,JVM中的并发是通过线程切换并分配时间片执行来实现的. 在任何一个时刻, 一个处理器内核只会执行一条线程中的指令. 因此, 为了线程切换后能恢复到正确的执行位置, 每条线程都需要有一个独立的程序计数器, 这类内存被称为“线程私有”内存.

2虚拟机栈(Java Stack)

虚拟机栈描述的是Java方法执行的内存模型: 每个方法被执行时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息. 每个方法被调用至返回的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程(VM提供了-Xss来指定线程的最大栈空间, 该参数也直接决定了函数调用的最大深度).

  • 局部变量表(对应我们常说的‘堆栈’中的‘栈’)存放了编译期可知的各种基本数据类型(如boolean、int、double等) 、对象引用(reference : 不等同于对象本身, 可能是一个指向对象起始地址的指针, 也可能指向一个代表对象的句柄或其他与此对象相关的位置, 见下: HotSpot对象定位方式) 和 returnAddress类型(指向一条字节码指令的地址). 其中long和double占用2个局部变量空间(Slot), 其余只占用1个

3本地方法栈

与Java Stack作用类似, 区别是Java Stack为执行Java方法服务, 而本地方法栈则为Native方法服务, 如果一个VM实现使用C-linkage模型来支持Native调用, 那么该栈将会是一个C栈

4java堆( Heap)

几乎所有对象实例和数组都要在堆上分配(栈上分配、标量替换除外), 因此是VM管理的最大一块内存, 也是垃圾收集器的主要活动区域. 由于现代VM采用分代收集算法, 因此Java堆从GC的角度还可以细分为: 新生代(Eden区、From Survivor区和To Survivor区)和老年代; 而从内存分配的角度来看, 线程共享的Java堆还还可以划分出多个线程私有的分配缓冲区(TLAB). 而进一步划分的目的是为了更好地回收内存和更快地分配内存.

5方法区

即我们常说的永久代(Permanent Generation), 用于存储被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小)

  • 运行时常量池
    方法区的一部分. Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项常量池(Constant Pool Table)用于存放编译期生成的各种字面量和符号引用, 这部分内容会存放到方法区的运行时常量池中(如前面从test方法中读到的signature信息). 但Java语言并不要求常量一定只能在编译期产生, 即并非预置入Class文件中常量池的内容才能进入方法区运行时常量池, 运行期间也可能将新的常量放入池中, 如String的intern()方法.

jvm内存模型的数据说明图

在这里插入图片描述
在这里插入图片描述
堆(heap):,前面已经说了他是最大的,也是最重要的一块区域,这里也称为逻辑堆,主要用来存放对象实例与数组,对于所有的线程来说他是共享的,对于Heap堆区是动态分配内存的,所以空间大小和生命周期都不是明确的,而GC的主要作用就是自动释放逻辑堆里实例对象所占的内存,而在逻辑堆中还分为新生代与老年代,用来区分对象的存活时间,在新生代中还被细致的分为 Eden SurvivorFrom以及SurvivorTo这三部分.

方法区(Method Area):方法区主要存储(类加载器)ClassLoader加载的类信息,在这里我们可以理解为已经编译好的代码储存区,所以存储包括类的元数据,常量池,字段,静态变量与方法内的局部变量以及编译好的字节码,等等

栈(stack):在每一个对象被创建的时候,在堆栈区都有一个对他的引用,在这里我们可以这样理解。

注:其中程序计数器,虚拟机栈和本地方法栈是线程私有。java堆和方法区是线程共享。

gc垃圾回收机制

GC的工作目的很明确:在堆中,找到已经无用的对象,并把这些对象占用的空间收回使其可以重新利用.大多数垃圾回收的
算法思路都是一致的:把所有对象组成一个集合,或可以理解为树状结构,从树根开始找,只要可以找到的都是活动对象,如果找不到,这个对象就应该被回收了。

年轻代 :
在年轻代中jvm使用的是Mark-copy算法,就像算法名字说的那样有两个步骤,第一是标记(Mark) 第二是copy(复制),Mark主要用于标记出还活着的实例,然后清除掉没有被标记的实例,释放内存,然后Copy部分则是将还活着的实例根据年龄拷贝到不同的年龄代,而jvm又是根据什么来区分年龄代,和实例存活与不存活的呢? 下面将对这个过程进行讲解

对于标记 与区分年龄代的技术 我们一般都是用到的都是引用计数器,在每一个对象中都含有引用计数器,都有引用指向对象的时候 引用计数器就会加1,不在被引用 计数器 减 1,对与垃圾回收的策略则是标记所有活着的实例,将没有被标记的实例全部回收 释放内存,

扫描二维码关注公众号,回复: 10795018 查看本文章

对于静态,我们都知道静态方法与静态变量是不会产生实例的,直接通过类的引用,使用 ClassLoader进行加载的类数据如前面所说是不存在逻辑堆里面的,直接存在于永生代里面也就是 方法区里面,这个类一旦被清除掉里面所有的静态变量都会被清除

当我们在 Object obj 的时候 向逻辑堆中的 Eden区域 申请内存,当Eden区域的内存不足的时候,这个时候会触发GC这个时候称gc为小型垃圾回收,每个实例都有一个独有的年龄,每个引用被经历过一次GC后就会年龄加一,同时就会将没有被清理掉的对象全都copy到上图的survivor1区域,如图1所示:

在这里插入图片描述
当第二次GC执行的时候就会使用Mark算法找到存活的对象,然后将他们的年龄加1,并且将他们拷贝到survivor2区域,然后执行GC,这样就可以实现survivor1 与 survivor2 两个一样大的区域进行交替使用,当对象的年龄足够大的时候,对象就会被移动到老年代,这里移动到老年代的标准由JVM的参数所决定

年老代 :
当GC被触发的时候 eden的对象会转到 survivor1 然后再次就会转到 survivor2 ,当survivor1的对象太大了 survivor2的区域无法容纳得部分就会转到Tenured的区域,当Tenured的区域也容不下的时候就会自动移动到年老代,在移动年老代的时候会先触发年老代上面的GC然后在将Tenured容纳不下的对象放入年老代,对于年老代的GC算法与年轻代的Mrak-copy算法有很大不同

gc算法

比较常见的垃圾回收算法有, 标记-清除(Mark-Sweep)算法、 复制算法、 标记-整理(Mark-Compact)算法、 分代回收算法等。

标记-清除算法
标记-清除算法是最基础的垃圾回收算法, 算法分为标记(mark)和清除(sweep)两个阶段, 首先通过上文提到的判断对象存活的方法, 来标记出所有可回收的对象, 标记完成后, 统一进行回收.

复制算法
复制算法将内存空间平分为两块, 每次只用其中的一块, 当这块内存空间用完了, 就将存活的对象复制到另外一块空间, 然后清空当前空间. 这样回收后内存不会存在不连续的空间, 解决了内存碎片的问题. 算法简单, 实现效率高. 但问题是每次只用一半的内存, 内存利用率低。

标记-整理算法
标记-整理(Mark-Compact)算法和标记-清除(Mark-Sweep)算法类似, 先标记存活的对象, 然后进行内存空间整理, 将存活的对象放到一起, 清空掉其余的空间。

分代回收算法
代回收算法根据对象存活周期的不同而将内存分为多个区域, 按各个区域存储对象的不同选择合适的垃圾回收算法. 像HotSpot虚拟机将内存分为新生代、老年代两部分, 在新生代每次垃圾回收, 只有很少的对象存活, 比较适合采用"复制"算法; 老年代对象存活率高, 没有其它空间做内存分配担保, 适合采用"标记-整理"和"标记-清除"算法

jvm内存模型参考博客:https://blog.csdn.net/zjf280441589/article/details/53437703
gc回收机制参考博客:https://blog.csdn.net/qq_33048603/article/details/52727991

发布了34 篇原创文章 · 获赞 1 · 访问量 3154

猜你喜欢

转载自blog.csdn.net/weixin_43700342/article/details/89057471