张国伟 发表于 4 天前

[JVM]JVM内存划分, 类加载过程, 双亲委派模型,垃圾采取机制

一. JVM内存划分

JVM就是java进程
这个进程一旦跑起来, 就会从操作体系这里, 申请一大块内存空间
JVM接下来就要进一步的对这个大的空间, 举行划分, 划分成不同区域, 从而每个与都有不同的功能作用
https://i-blog.csdnimg.cn/direct/74e3a6444f5e444cbeca42683a77eae0.png
1. 堆

整个内存区域中, 最大的区域
放的是代码中new出来的对象和成员变量
2. 栈

分为JVM虚拟机栈和本地方法栈
栈, 保存了方法中的调用关系 和 局部变量
JVM虚拟机栈, 是java利用的
本地方法栈, 是给本地方法利用的
3. 元数据区

放的是类对象 Test.class 和 类中的方法 和 常量池
代码中的每个类, 在jvm上运行的时候, 都会有对应的类对象
类有一些方法, 每个方法, 都代表了一系列的"指令聚集"(JVM字节码指令)
4. 步伐计数器

是内存区域中最小的区域, 只需要保存当前要执行的下一条指令的地点(这个地点就是元数据区内里的一个地点)
https://i-blog.csdnimg.cn/direct/07324a63be6049baaefdf3f68a53c696.png
上述代码:
Test类的类对象, 在元数据区
a, 是成员变量, 在堆上
t2, 是成员变量, 在堆上, 存放的是Test2实例的地点
new Test2(), 在堆上
s, 是成员变量, 在堆上, 存放指向"hello"的地点
“hello”, 是常量, 存储在常量池中, 在元数据区
b, 是静态成员变量, 成为了类属性, 在元数据区
t, 是局部变量, 在栈上, 存放的是Test实例的地点
new Test(), 在堆上
https://i-blog.csdnimg.cn/direct/0a8af9ddba404930b1584a4580742095.png
上四个区域, 堆 和 元数据区, 是整个进程只有一份, 每个线程共享
栈 和 步伐计数器, 是每个线程都有一份, 各个线程不可共享的
二. 类加载过程

我们写的代码, 是.java文件, 存储在硬盘上
javac编译器将.java文件, 编译成.class文件
类加载的过程, 就是将.class文件加载到JVM中, 得到类对象
1. 加载

在硬盘上, 找到对应的.class文件, 读取文件内容
2. 验证

检查.class内里的内容, 是否符合要求
https://i-blog.csdnimg.cn/direct/c5ad871899c5419c8f68ca30677f959b.png
这是java虚拟机规范中的.class文件的格式
把读取的内容, 往这个格式里套, 假如能套用, 就没有问题
3. 准备

给类对象, 分配内存空间(元数据区)
把这个空间中的数据都填充成0
4. 解析

针对字符串常量初始化
把刚才的.class文件中的常量内容, 取出来, 放到"元数据区"
5. 初始化

针对类对象 静态成员举行初始化, 执行静态代码块
(不是针对对象初始化, 和构造方法无关)
此时, 类对象就搞定了
三. 双亲委派模型

双亲委派模型, 是类加载中的第一步, 找.class文件的过程, 根据"全限定类名"找到对应的.class文件
“全限定类名”, 就是报名 + 类名 :String => java.lang.String
“类加载器”, 可以当做JVM中包含的一个特定的模块, 这个模块就是负责类加载的工作
JVM内置了三个类加载器:

[*]BootstrapClassLoader
负责加载标准库中的类(爷爷)
[*]ExtentionClassLoader
负责加载JVM扩展库的类(父亲)
[*]ApplicationClassLoader
负责加载第三方库的类, 和你本身写的代码的类(儿子)
这个的父子关系,是通过类加载器中存在一个parent如许的字段, 指向本身父亲
工作过程:
例如找我本身写的类: java.Test
https://i-blog.csdnimg.cn/direct/8e22af1669d14700a7d5d59b130848f5.png
简单一句话概括, 就是拿到任务, 先交给父亲处置处罚, 父亲处置处罚不了, 再本身处置处罚
上述过程, 主要为了应对这个场景:
比如本身代码里写了一个类, 类的名字和标准库/扩展库冲突了, JVM会确保加载的类是标准库的类, 就不加载你本身写的类了
四. JVM的垃圾采取机制GC

1)步伐计数器, 不需要额外采取, 线程烧毁, 自然采取
2)栈, 不需要额外采取, 线程烧毁, 自然采取
3)元数据区, 存的是类对象, 一样平常不需要采取
以是, GC采取的是内存, 更准确说, 是对象, 采取的是"堆"上的内存
GC的流程:

[*]找到谁是垃圾
[*]释放对应的内存
1. 找到需要采取的对象

一个对象, 什么时候创建, 机遇往往是明白的, 但是什么时候不再利用, 现实往往是模糊的
在编程中, 要确保, 代码中利用的对象, 都是有效的, 万万不要出现"提前释放"的情况
以是, 我们判定是否采取的守旧办法, 就是判定某个对象, 是否存在引用指向他
https://i-blog.csdnimg.cn/direct/1b14c1e1f57a47a38a28b2305631726c.png
https://i-blog.csdnimg.cn/direct/1c6651bfe253402a99173030497fd257.png
具体怎么判定某个对象, 是否有引用指向?
下面先容两种方式
** 1. 引用计数**
留意: 这个方法不是JVM接纳的方案, 而是Python/PHP的方案
https://i-blog.csdnimg.cn/direct/bea9449ed56e4b849f2303b48e1072a2.png
这种方法存在两个缺陷:
1.消耗额外的存储空间
假如对象比力大, 浪费的空间还好, 假如对象比力小, 空间占用就多了
2.存在**“循环引用”**问题
https://i-blog.csdnimg.cn/direct/0d9b7aab744f4f578b45f20769f36b89.png
在内存中是如许的:
https://i-blog.csdnimg.cn/direct/ede2ed3a303241d08341d2fde0b3f011.png
如今有下面代码:
https://i-blog.csdnimg.cn/direct/13b09492544b4c9cbde3cf55cf9f1047.png
https://i-blog.csdnimg.cn/direct/9ccc0746351c4ffcb286a88e0564edfa.png
https://i-blog.csdnimg.cn/direct/87faa7e1680f44ccaf8b28970abbc0b1.png
https://i-blog.csdnimg.cn/direct/914182e9459c4ef0b22edba926522e69.png
此时, 俩对象相互指对方, 导致两个对象的引用计数, 都不为1, (不为0, 就不是垃圾), 但是外部的代码, 也无法访问到这俩对象, 出现"循环引用"
2. 可达性分析
留意: 可达性分析是JVM采取的方案
JVM把对象之间的引用关系, 理解成了一个**“树形结构”**
JVM就会不绝地周期性遍历如许的结构, 把所有能够遍历访问到的对象标记成"可达", 剩下的就是"不可达"
假设对象之间存在如许的树形引用关系:
https://i-blog.csdnimg.cn/direct/efe63a2b009f4f98a88541eb3ff64153.png
此时, 从a出发, 任何一个结点都是可达的, 没有垃圾
假云云时c中的引用f = null, 相称于c.right = null, 那么f 就不可达了, 此时后续举行遍历时, 就会把f标记成垃圾了
假云云时a中c的引用c = null, 相称于a.right = null, 那么c和f都不可达了, 此时后续遍历, 就会把c和f标记成垃圾
由于可达性分析, 需要消耗肯定的时间, 因此java垃圾采取, 没法做到"实时性", JVM提供了一组专门负责GC的线程, 不绝地举行扫描工作
2. 释放垃圾的策略

1. 标记清除
直接把标记为垃圾的对象对应的内存, 释放掉(简单粗暴)
https://i-blog.csdnimg.cn/direct/f876e68d0c6a491c87d3a86bc3b20aff.png将灰色的采取掉
如许的做法, 就会存在"内存碎片", 空闲内存被分成一个个小的碎片了, 后续很难申请到大的内存
申请内存, 都是要申请"连续"的内存空间的
2. 复制算法
https://i-blog.csdnimg.cn/direct/3219f16ea38b42ab88d1a1de5b77aa85.png
比如, 要释放246, 保存135, 不会直接释放246, 而是先把135拷贝到另一块连续的内存上去
https://i-blog.csdnimg.cn/direct/a33de74f7c964c3f8fd7810c3d6d83af.png
虽然内存碎片问题办理了, 但是空间浪费太多了
3. 标记整理
https://i-blog.csdnimg.cn/direct/7541f0186f5a44438c660c8243d6ef79.png
接纳类似"顺序表删除中间元素"的方法, 向前搬运
先采取2, 把3向前搬运, 再释放4, 5向前搬运…
但是, 如许的搬运, 时间开销很大
着实, JVM现实的方案, 是综合上述的方案, 分代采取
根据对象的’‘年龄’'选择不同的方案
某个对象, 履历了一轮GC之后, 还是存在, 那么GC + 1
现实上堆区是分成Young区和Old区
https://i-blog.csdnimg.cn/direct/d1cb16ee1dd2476b84af432211249d66.png
Eden: 伊甸区
s0,s1: 生存区/幸存区
把创建的对象, 放在伊甸区中, 伊甸区大部分的对象, 生命周期都是比力短的, 第一轮GC到达的时候, 就会成为垃圾
把第一轮剩下的对象, 通过复制算法, 复制到生存区
每经过一轮GC, 生存区中都会镌汰掉一批对象, 剩下的通过复制算法, 进入到另一个生存区, 同时, 伊甸区中生存下来的也复制到这个生存区中
某些对象, 履历了很多轮GC, 都没有称为垃圾, 就会复制到Old区
Old区的对象, 也是需要GC的, 但是老年代的对象生命周期比力长, 就可以低落GC的扫描频率
对象在Old区, 就通过标记整理方法来举行采取

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: [JVM]JVM内存划分, 类加载过程, 双亲委派模型,垃圾采取机制