【笔记】深入理解JVM机制

  • 🎥 个人主页:Dikz12
  • 📕格言:吾愚多不敏,而愿加学
  • 欢迎大家👍点赞✍评论⭐收藏

目录

 JVM 运⾏流程图

 JVM 中内存区域划分

 方法区 / 元数据区

 栈

 程序计数器

 本地方法栈

内存区域总结 

 JVM 中类加载过程

双亲委派模型

JVM 中的垃圾回收机制

找到垃圾 

 引用计数 (Python,PHP)

 可达性分析(Java) 

回收垃圾

标记清除

 复制算法

标记整理

堆的新生代 和 老生代


 JVM 运⾏流程图

 JVM 中内存区域划分

一个运行起来的Java进程,就是一个JVM虚拟机,就需要从操作系统申请一块内存,然后把这个内存,划分成不同区域,每个区域都有不同的作用.

比如:学校这么大场地,就相当于申请了一大块内存空间.

 方法区 / 元数据区

jdk1.7及其以前叫做 方法区;从jdk1.8开始叫做元数据区.

元数据区存储内容:用来存储被虚拟机加载的类信息、常量、静态变量. (这里存储的是类对象, .class文件,加载到内存之后,就成了类对象)

堆存储的内容:程序创建的所有对象都保存在堆里.(就是代码new的对象)

堆⾥⾯分为两个区域:新⽣代和⽼⽣代.


 

具体在垃圾回收机制里说明!!!

 栈

 栈存储的内容:代码执行过程中,方法之间的调用关系.(a->b->c)

每个栈帧里就包含了方法的入口, 方法的返回的位置,方法的形参,方法的返回值,局部变量......等

 程序计数器

 程序计数器是一块比较小的空间,主要就是存放一个“地址”,下一条要执行的指令,在内存中的哪个地方.

 刚开始调用方法,程序计数器,记录的就是方法入口的地址;随着一条一条的执行指令,每执行一条,程序计数器的值就会自动更新,去指向下一条指令.

class Test{

    public void a(){...}
    public void b(){...}
}

这里的方法a 和 方法b都会被编译成二进制的指令,就会被放到.class文件中.

 本地方法栈

本地方法栈,指的是使用nativa关键字修饰的方法;这个方法不是使用Java实现的,而是在JVM内部通过C++代码实现的. (JVM内部的C++代码调关系) 

内存区域总结 

 

 JVM,是一个运行Java进程,一个进程可以包含多个线程,内存区域有如何划分?

虚拟机栈及程序计数器,都是每个线程都有一份.

堆和元数据区在jvm进程中只有一份,线程共享.

JVM 有10个线程,就会有10个虚拟机栈,也会有10个程序计数器.

 JVM 中类加载过程

对于一个类来说,它的生命周期如下:

 课本上 和 官方文档 把这个类加载的过程,主要是分成了五个部分.

1.加载.   找到.class文件,打开文件,读取文件内容. 代码中,会给定某个类的“全限定类名”,jvm就会根据这个类名,在一些指定的目录范围内查找.     (全限定类名:java.lang.Sring 、java.util.ArrayList)

2.验证.   检验 .class 文件是一个二进制的格式.(某个字节,都是有特定的含义)     

                           

java8规范要求:Chapter 4. The class File Format

 3. 准备.    给类对象分配内存空间,这里只是分配内存空间,还没有初始化呢,此时这个空间上的内存数值,全是0.

4.解析.     针对类对象中包含的字符串常量进行处理,进行一些初始化操作. java代码中用到的 字符串常量,在编译之后也会进入到.class文件中.

比如: final String str = "test".

在.class文件的二进制指令中,也会有一个 str 这样的引用被创建出来;由于应用本质上保存的是一个变量的地址,在 .class文件是一个文件,不涉及到内存地址,就会先设置成一个“文件的偏移量”通过偏移量,就能找到“test”这个字符串所在的位置.(真正被加载到内存的时候,再把这个偏移量替换成真正的内存地址)

5. 初始化.   针对类对象进行初始化,就是把类对象中需要的各个属性都设置好.

双亲委派模型

1.BootStrap :启动类加载器.
2. Ext ClassLoader :扩展类加载器。加载 lib/ext ⽬录下的类.
3. App ClassLoader:应⽤程序类加载器.
4. ⾃定义加载器:根据⾃⼰的需求定制类加载器.

 JVM中,内置了,三个类加载器:

过程: 

1. ⼀个类加载器收到了类加载的请求,它⾸先不会⾃⼰去尝试加载这个类.

2. ⽽是把这个请求委派给⽗类加载器去完成,每⼀个层次的类加载器都是如此.
3. 所有的加载请求最终都应该传送到最顶层的启动类加载器中
4. 只有当⽗加载器反馈⾃⼰⽆ 法完成这个加载请求(它的搜索范围中没有找到所需的类)时,⼦加载器才会尝试⾃⼰去完成加载.

5.当子类加载器AppClassLoader,也没找到就会抛出一个ClassNotFoundException.

JVM 中的垃圾回收机制

 GC垃圾 回收的目标,其实就是 内存中的对象. 对于Java来说就是 new出来的对象.

栈里放的是局部变量,是跟随栈帧的生命周期走的.(方法执行结束,栈帧销毁,内存自然释放)

静态变量,生命周期就是整个程序,始终在,就意味着,静态变量时无需释放的.

GC可以理解两大步骤:  1. 找到垃圾.        2.释放垃圾  

找到垃圾 

 引用计数 (Python,PHP)

为什么Java不使用引用计数???

1.比较浪费内存.

计数器,也要有两个字节.如果对象本身就很小,这个计数器占据的空间比例就很大了.

比如对象本身就两个字节,计数器占据的空间就是50%. (如果对象小并且多,计数器占据的空间就难以忽视了)

2.引用计数机制,存在“循环引用”问题.

 可达性分析(Java) 

 有一个 或者 一组线程,周期性的扫描我们代码中的所有对象. 

从一些特定的对象出发,尽可能的进行访问的遍历,把所有都能够访问到的队象,都标记成"可达"

反之,经过扫描之后,未被标记的对象,就是“垃圾”了.

可达性分析,是周期性进行的,所以是比较消耗系统资源,开销比较大.是时间换空间的手段 

回收垃圾

标记清除

简单粗暴的方式,比如,申请了一块内存空间,上面有一些对象,通过可达性分析发现 2 和 4是垃圾, 就直接把对应的对象的内存,直接释放掉,就是标记清除方案.

 这个方案其实非常不好,会产生很多的内存碎片;释放内存,目的是为了让别的代码能够申请. 申请内促,都是申请到“连续”的内存空间 ; 随着时间的推移,内存碎片的情况,就会越来越严重,最会导致后续内存申请举步维艰.

 复制算法

 通过复制的方式,把有效的对象,归类到一起,在统一释放剩下的空间.

比如,一块内存空间先一分为二,一次只用一其中一半, 里面有一些对象,假设1,3,5垃圾,通过复制算法,把2 和 4 复制到另外一边,就可以把左侧的整体释放掉.

 

 这个方案可以有效的解决内存碎片问题,缺点也是很明显的;

 1.内存要浪费一半,利用率不高.      

 2.如果有效对象非常多,复制的开销就很大了.

标记整理

 既能够解决内存碎片的问题,又能处理复制算法中的利用率.

过程就类似于顺序表删除元素的搬运操作. 

比如,一块内存空间,通过可达性分析,1,3,5是垃圾, 把2搬运到1的位置.... 

 

 

搬运开销仍然很大!!

实际上,JVM 采取的释放思路,是上述基础思路结合体.(让不同的方案,扬长避短) 

堆的新生代 和 老生代

垃圾回收只是针对堆进行的, 堆的内存空间会分成两部分,不是等分的,具体怎么分不一定,左边称为“新生代”,右边称为“老年代”.

新生代:又进一步的划分,分为 幸存区 和 伊甸区.

幸存区:等分的两部分,每次只用一块. 正好就是复制算法的体现.

伊甸区:放的是刚 new出来的对象.(还没有经过可达性扫描)

 

 经验规律: 从对象诞生,到第一轮可达性分析扫描,这个过程中虽然时间不长,基本就是 毫秒 - 秒(这个时间维度,对于程序的眼中也挺长了),在这个时间里大部分的对象都会成为垃圾.

 1.伊甸区 -> 幸存区 (复制算法)

       每一轮GC扫描之后,都会把有效的对象,复制到幸存区中,伊甸区就可以整个释放了. 由于经验规律,真正需要复制的对象不多,非常适合复制算法.

2. GC 扫描线程也会扫描 幸存区. 就会把活过GC扫描的可达对象,复制到幸存区的另一部分. 幸存之前的复制,每一轮会复制多个对象,每一轮也可以淘汰掉一些对象.

3. 当这个对象已经在幸存区存活很多轮GC扫描之后,JVM 就认为这个对象,短时间内是应该释放不掉,就会把这个对象复制到老年代.

 4. 进入老年代的对象,虽然也会被GC 扫描,扫描频率就会比新生代,低很多.

   (也是为了减少GC扫描的开销,要挂早就挂了!!!)

   

  • 95
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 58
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值