文章目录
JVM是Java程序运行的基础,整个JVM的体系结构非常复杂,但是幸运的是经常使用到的只有三个部分:内存划分、类加载和垃圾回收机制。我们接下来就向大家详细的介绍JVM里对于这三部分的具体实现:
JVM内存划分
Java程序是一个名字为Java的进程,这个进程就是所说的“JVM”。在Java程序运行前,JVM就会先向操作系统申请一大块内存空间,这个内存空间内部又划分为几个不同的部分:
JVM类加载
为什么需要类加载?
Java程序在运行之前需要先编译,由 .jaca 文件编译为 .class 文件(二进制字节码文件),运行的时候 JVM 就会读取相应的 .class 文件并解析其中的内容,在内存中构造出类对象并进行初始化。
类加载的过程
-
加载
找到 .class 文件,读取文件内容并按照 .class 规范的格式来解析
-
连接
-
验证
检查当前 .class 文件里的内容格式是否符合要求
-
准备
给类里的静态变量分配内存空间
-
解析
初始化字符串常量:把符号引用(占位符)替换成直接引用(内存地址)
比如:代码中有一行 String s = “hello”;在类加载之前 “hello” 这个字符串常量没有分配内存空间,没有内存空间 s 也就无法保存 “hello” 的真正的地址,只能用占位符标记一下;等给 “hello” 分配内存空间后,再用真正的地址来替换之前的占位符。
-
-
初始化
针对类进行初始化,初始化静态成员、执行静态代码块、加载父类。
何时触发类加载?
使用到一个类的时候就要触发类加载(类并不是程序一启动就加载了,而是在第一次被使用的时候才会加载)
比如:
- 创建了这个类的实例
- 使用了类的静态方法、静态属性
- 使用了类的子类(加载子类会触发加载父类)
双亲委派模型
双亲委派模型:决定了按照啥样的规则到哪些目录下去找 .class 文件
JVM加载类是由类加载器(class loader)这样的模块来负责的,JVM自带了多个类加载器:
- Bootstrap ClassLoader:负责加载标准库中的类
- Extension ClassLoader:负责加载JVM扩展的库的类
- Application ClassLoader:负责加载项目里自定义的类
JVM的垃圾回收机制(GC)
什么是垃圾回收?
我们的内存空间是有限的,如果只申请内存空间但是不释放就会造成很严重的问题。而垃圾回收机制呢就是:让JVM自动判定你申请的内存啥时候需要释放,即当JVM认为这块内存不再被使用了就会释放。
这样的机制可以减轻程序员的负担也能更好的避免忘记释放内存空间的问题,毕竟程序员只负责申请内存空间即可,释放内存空间的工作交给了JVM来完成。
GC回收哪部分内存?
栈:存放方法间的调用关系----------释放时机确定,不必回收
堆:存放程序中所有的对象----------GC进行回收
方法区:存放加载好的类 ----------加载好后不需要回收
程序计数器:存放下一条指令的地址---------是一块固定的内存空间,不必回收
回收机制
怎么找出垃圾?
如果一个对象再也不被使用了,那么它就是垃圾。
在Java中,对象的使用需要凭借引用。如果一个对象已经没有任何一个引用可以指向它,那么它就无法被使用,就变成了垃圾。
引用计数
引用计数:给每个对象都加一个计数器,通过这个计数器来表示“当前对象具有几个引用”
每多一个引用指向该对象,计数器就+1;每少一个引用指向该对象,计数器就-1;
当计数器数值为0的时候,就证明当前对象没有引用了、不能被使用了、可以被释放了。
可达性分析(JVM采用)
约定一些特定的对象作为(GC roots),每隔一段时间,从 GC roots 出发进行遍历,看看当前哪些对象是能被访问到的。能被访问到的对象就称为“可达”;访问不到的对象就称为“不可达”。包含“可达”对象的类不做处理,包含“不可达”对象的类要被确定为垃圾。
怎么清理垃圾?
标记清除
灰色的部分被标记为“垃圾”。标记出垃圾之后,直接把对象对应的内存空间释放。
复制算法
在申请内存空间的时候直接申请想要空间大小的2倍,并把这块内存空间分为俩部分(左侧和右侧),使用左侧空间时,右侧不用;使用右侧空间时,左侧不用。
在回收垃圾时,不再是原地释放了,而是把“非垃圾”(可以继续使用的对象)拷贝到另一侧,然后再把之前使用的这一半空间整个释放掉。
标记整理
像顺序表的删除元素一样,进行搬运操作。如果前面的一小块内存空间被释放掉,则把它后面的数据依次向前搬运,占用掉已经释放了的这块内存空间。
分代回收
特殊情况: 如果对象是一个非常大的对象,则直接放入老年代。
因为:
- 大的对象进行复制算法的时候开销太大了
- 大的对象创建出来相对较难,好不容易创建出来后不会让它轻易的销毁。