JVM内存模型

1.jvm基本结构

类加载子系统: 将class文件加载到jvm中

方法区: 加载class信息/运行时常量池存放在方法区

java堆: 基本上所有的java实例/对象都是存放在java堆中
方法区以及java堆都是公有

java栈: 普通方法的一些信息(方法入参/局部变量/中间临时数据/返回数据/异常状态处理)都是存放在栈中(私有)

本地方法栈: 类似java栈,不过不是普通方法而是nativeMethod

寄存器:用来指向正在被执行的位置

直接内存: 对外内存,向系统申请内存空间(与java堆相比,读写方法速度较快,但是申请比较耗时)

垃圾回收: 用来回收方法区,java堆,直接内存

执行引擎: 负责执行JVM中的字节码

2.JVM类加载基本流程

1.加载classLoader

通过类的全路径名,获取类的二进制数据流,获取类的信息,创建java.lang.Class类的实例

2.验证

判断字节码是够合法,规范性

主要涉及: 格式/语义/字节码/符号引用验证

3.准备

分配对应的内存空间

4.解析

将符号引用转为直接引用

5.初始化

开始执行字节码,已经被加载到系统中

符号引用 :  用符号来描述引用的目标直接关系

直接引用 :  通过Class文件加载到内存之后,通过对符号引用的转换,产生直接引用

为啥会有直接引用?

我们的java类编译成class文件之后,并不知道引用地址,所以用符号来代替

在class文件解析过后,会将符号转换为直接引用,用#12,#37来直接指向引用地址

3.虚拟机栈

虚拟机栈主要是为了执行方法服务的,用来描述方法执行的内存模型

主要内容: 栈帧(局部变量表,操作数栈,栈数据区)

入栈: 方法调用

出栈: 方法返回

栈顶: 正在执行的方法

private static void count(long arg1, long arg2, long arg3, long arg4, long arg5) {
        long num1 = 1;
        long num2 = 2;
        long num3 = 3;
        long num4 = 4;
        long num5 = 5;
        long num6 = 6;
        long num7 = 7;
        long num8 = 8;
        count++;
        count(arg1, arg2, arg3, arg4, arg5);
    }

我们用jclasslib插件来看看以上代码在栈帧中有哪些东西

 以及PC计数器用来记录执行的代码

4.java堆

几乎所有的对象都是存储在java堆上面,而且Java堆完全自动化管理,通过垃圾回收机制,垃圾对象会被自动清理

主要划分为:新生代(Eden区,S0和S1)和老年代

新生代:老年代 = 1:2 默认比例

Eden:S0:S1 = 8:1:1 默认比例

在大部分情况下,对象首先会分配在Eden区,在一次新生代GC回收后,如果对象还存在,则会进入S1或S0中,之后每经过一次新生代GC,如果对象还存活的话,它的年龄+1,当年龄达到一定条件后(默认为16),会被认为是老年代对象,进入老年代区

5.方法区

方法区的大小决定系统中能存储多少个类

存储类的信息/方法信息/常量池

1.8之前使用永久代

1.8之后使用元空间

永久代:  内存永久保存区域,主要用于存在Class(类),在类被加载的时候放入永久代区域,GC不会对永久代区域进行清理,所以导致Class越来越多的时候,导致OOM异常

元空间: 元空间本质类似永久代,主要区别是,元空间并不在虚拟机中,而是使用堆外的直接内存,因此默认情况下,虚拟机会耗尽可用的系统内存

6.垃圾回收(GC)

可触及性: 从根节点开始,可以达到某个对象

可复活性: 对象引用被释放,但是可能在finalize()函数中被复活

不可触及性: 由于finalize()函数只会执行一次,错过这一次复活的对象,则变成不可触及状态

引用关系: 

1.强引用: 一般来讲就是程序中的new对象

2:软引用(java.lang.ref.SoftReference): 当堆空间不足的时候,才会被回收,因此软引用不会引发内存溢出

3.弱引用(java.lang.ref.WeakReference): 当GC的时候,只要发现弱引用,无论系统空间是否不足,都会回收

4.虚引用(java.lang.ref.PhantomReference): 如果对象被虚引用,其实和没引用一样.  虚引用必须和引用队列在一起使用,它的作用是用于跟踪GC回收过程

对象分配过程:

逃逸分析: 就是指分配的对象是否逃出了自己作用域,那么就可以理解为该分配的对象已经不满足了线程私有的前提,因此不会栈上分配(默认开启)

标量替换: 把聚合量分解为标量, 通俗理解把对象分解为成员变量存储在栈帧或寄存器上(默认开启)

那么YoungGC又是怎么操作把垃圾清理掉的呢

复制算法: 将Eden区存活的对象复制到Survir区,并且清空Eden区

那么什么情况下会进入老年代呢

1. BigObject当对象达到一定程度(默认为0)

2. 当对象的年龄达到了阈值(默认15)

3. 当SurviveTo区空间不足的时候

以上情况都会将对象分配到老年代

那么老年代区的对象又是怎么清除(GC)的呢

标记压缩算法: 首先标记存活的对象,然后将存活的对象压缩到内存的一端,然后再清理所有存活对象之外的空间

 

标记清除算法: 标记存活的对象,直接将不存活对象的直接清理 

7.垃圾回收器

7.1串行回收器 - Serial(JVM Client)模式下默认的回收器 

    特点: 1.只使用单线程的GC 2.独占式GC

 

 言外之意就是当串行回收器(Serial)准备GC的时候,所有的其他的线程都需要停止,等我GC完毕之后,其他的线程再继续执行

7.2 并行回收器 - ParNew&ParallelGC&ParallelOldGC&CMS (将回收器多线程化)

 

ParNew&ParallelGC都是新生代并行回收器,采用的都是复制算法

ParallelOldGC 老年代并行回收器(标记压缩算法)

CMS 老年代并行回收器(标记清除算法)

7.3 CMS - 并行回收器

 

 

初始标记: 标记根对象(停止程序)

并发标记: 标记老年代所有的对象

预清理: 由于CMS回收用于老年代垃圾回收,所以预清理主要是为了避免和YoungGC发生时间碰撞

重新标记: 由于之前的并发标记并没有进行STW(停止程序)的操作,所以说新生代可能存在YoungGC的操作,并且会有新的对象进入老年代,那么就需要重新标记(一般来讲需要重新标记的对象不会很多),过程中必须STW(停止程序)

最后再进行并发清理和重置

7.4 G1回收器(JDK1.7之后使用)

特点:优先回收垃圾比例比较高的区域

G1回收器将堆空间分为多个区域,每次收集部分区域来减少GC的时间

主要步骤:

 

以上过程都是循环的过程一直在收集,主要我们来看看并发标记有哪些操作

 

1.初始化标记: 标记根对象(同时伴随着第一阶段的YoungGC并且STW)

2.根区域扫描: 由于上一步进行了YoungGC,所以这步主要是扫描SurvivorTo区存活的对象,如果对象满足进入老年代,直接进入老年代

3.并发标记: 分区标记存活的对象

4.重新标记: 由于整个收集器都是并行操作,需要重新标记新对象并且STW

5.独占清理: 由于G1收集器分区收集,这步主要是为了计算出每块区域中垃圾对象的比例,同时STW

 6.并发清理: 主要是用于清理垃圾已经填满的区域块

 

那么以上步骤我们收集了堆中的所有对象,并且分区管理了,需要将垃圾比例高的区域给清理,并且将剩余的对象重新移到其他区域来减少内存碎片,而且由于循环的方式回到第一阶段YoungGC,那么Eden区的空间会全部清空,再次收集

最终才是我们收集之后的内存情况! 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值