学习笔记---JVM 简单分析

个人粗浅的认为,JVM 的学习比较抽象,基本都是建立在概念上的。JVM 可以从两方面分析:

  1. 软件层面机器码的翻译
  2. 内存管理

软件层面机器码的翻译: Java 号称一次编译到处运行,是建立在对应与不同版本的JVM上的,对应不同OS的JVM 可以将编译好的文件代码翻译成对应的OS版本的机器码,来达到实现的;

内存管理:上图:
在这里插入图片描述1

  1. 本地方法栈:C语言方法运行时数据存储对应的内存区域,比如 Thread.Start 启动时,会调用c语言的线程方法;因此此处也会发生OOM;

  2. 程序计数器:记录编译后的机器码的执行行号与地址:多线程切换时,需要记录每个线程的执行代码位置;

  3. 方法区:存储类信息,静态变量,常量以及JIT编译后的类信息等;也会发生OOM;

  4. 虚拟机栈:以线程为单位,调用一个方法就创建压栈,栈针代表最新的数据;栈里面存储着局部变量表,动态链接(没搞懂),操作数栈,方法出口等信息,其中局部变量表就是存储基本变量或者引用变量的地址值,操作数栈作用在于运行时计算数值,最终返给局部变量表中,方法出口记录调用方法的出口;当调用的方法过多,造成栈的深度过大,则会发生stack overflow(栈溢出);当分配给总的线程所耗费的内存过大超过虚拟机栈内存,则会发生OOM;

  5. 堆:堆是创建对象存储的地区;堆的区域划分,上图:
    在这里插入图片描述
    JVM 将内存模型设计成原因,牵扯到GC的回收,而GC的回收策略实现了不同的回收算法,先来说下GC的回收前提操作:
    在这里插入图片描述
    主要是申请堆内存的时候,发现不够的话,则GC:检索出垃圾对象,回收空间;那么如何判断垃圾:
    1.引用计数法:无法解决循环依赖的对象问题;
    2.可达性算法:
    在这里插入图片描述

  6. 标记+清除算法:低效+内存碎片;首先扫描堆中所有的对象,发现垃圾对象则标志,然后清除;

  7. 标志+整理(标志+清除+压缩):低效,但是没有内存碎片:首先扫描堆中所有的对象,发现垃圾对象则标志,然后清除,最后将用到的内存区域移动在一起;

  8. 复制:高效,没有内存碎片,但是浪费空间;以可达性算法为基础:从根部开始,顺着链往下,发现一个则复制到新的内存区,这样没有GC-ROOT的对象就会被放弃掉,被GC线程回收;
    回到前面的设计原因,要知道发生GC的时候防止由于并发产生各种不可描述的后果,会启动STW机制,停掉所有的用户线程,执行GC线程回收,频繁这么操作的话则导致程序性能相当差(GC调用最终就是减少STW),采用分代算法,根据每一个Bean的生命周期不一样的特点,采取这种模型的:当申请创建小对象的时候,会在伊甸区申请内存对象;下一次申请发生伊甸区的内存不够时,则判断是不是垃圾对象,不是则复制到生存区1,然后在伊甸区申请区域;同理,下一次申请发生伊甸区的内存不够时,判断伊甸区+生存区1的对象是不是垃圾对象,不是则复制到到生存区2,依次循环,总是保留一个空的生存区,在伊甸区申请;到达一定条件后,则将这些对象复制到老年区,这里有个老年区担保机制,如果发现老年区的连续控制不够则触发 MajorGC ,这里采用的是标志清除或者标志整理的算法的,如果此时还不够,则不允许复制过来,如果伊甸区申请的区域不够,则触发OOM;
    当申请大对象时,则直接在老年区申请区域,这样往往很浪费内存,导致频繁GC的几率很大,进而导致STW,程序执行性能下降,笔者曾经犯过这个错误,后来学习了这部分知识才晓得;
    另外,当年轻代的相同年龄的对象之和>生存区的一半的空间的时候,也会复制到老年区的;

内存规整性:在申请内存区域的时候会考虑到这个问题,从而引出指针碰撞以及空闲列表:

  1. 指针碰撞:当堆存在足够的连续性大小的内存区域的时候,意思是说,空闲的内存在一边,占用的内存在一边的,不存在内存碎片的时候(采用复制或者标志+整理)采用的申请区域策略;
  2. 空闲列表:当采用的是标志+清除算法时,铁定会产生内存碎片,这时JVM底层在用记录每一个对象的引用地址来记录,当下一次申请的时候则根据空闲列表的记录来申请内存;

程序执行会因为并发导致数据的安全问题,同样的当并发申请内存区域时候,也要做一定的处理:
1.同步: CAS机制;
2.TLAB:在堆区为每一个线程划分一个特定区域,在里面申请区域;
这样就可以保证内存区域的独立性了;

个人粗浅的认为:申请一个内存空间的时候就就牵扯到上面的内容了;

另外这里牵扯到另外概念:强引用,软引用,弱引用,虚引用:
强引用:就是主要存在 GC-ROOT ,不管怎么样,都不会回收的;
软引用:发生OOM前,执行判断引用类型,如果是软引用,则回收;
弱引用:主要发生GC,则回收;
虚引用:直接上高手的图:
在这里插入图片描述

另外一个问题,当GC时,发现对象不可达的时候,不一定会回收这个对象的,原因在于可能这个对象重写 finalize() 方法;在申请内存不够的时候,触发GC,GC首先检查标志对象是不是没有GC-ROOT以及重写了 finalize() 方法,立马回收;如果重写了则将其加入到队列里面去,由JVM底层创建一个线程去执行这个方法,如果在这个方法里面又重新 GC-ROOT 的话,则该对象移除队列,起死回生了;但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值