JVM介绍
JVM介绍
一.JVM内存区域划分
1.内存区域介绍
1)程序计数器(比较小的空间)
保存了下一条要执行指令的地址。此处的程序计数器不再是CPU的寄存器了,而是内存空间。并且,此处的下一条要执行的指令是Java的字节码(不是CPU的二进制的机器语言)
2)堆
JVM上最大的空间,new出来的对象都在堆上。
3)栈
函数中的局部变量,函数的形参,函数之间的调用关系。
Java虚拟极栈:JVM之上运行的Java代码的方法调用关系
本地方法栈:JVM里头,C++代码的函数调用关系。
4)元数据区(方法区)
Java程序中的指令(指令都包含在类的方法中)
保存了代码中涉及到的类相关的信息
类的static属性
在一个Java进程中,元数据区和堆是只有一份的(同一个进程中的所有线程都是共用一份数据的),程序计数器和栈可能有多份,当一个Java进程中有多个线程的时候,每个线程都有自己的程序计数器和栈
线程就代表一个执行流,每个线程就需要保存自己的程序计数器,每个线程也需要记录自己的调用关系。
2.代码中变量位于的区域
类似于这样一段的代码,需要明确其中变量存在于JVM内存区域的哪个区域。一个变量处于哪个内存区域,这个和变量是不是“内置类型”无关,而是和变量的形态有关。
(1)局部变量:栈
(2)成员变量:堆
(3)静态成员变量:元数据区(方法区)
a,b都是成员变量,均在堆区。
c,d是静态成员变量,均在元数据区(d是new了一个对象,new出来的对象是在堆区,但是把这个堆上的内存地址,赋值给了d引用类型的变量)
e,f都是局部变量,均在栈区(f也是new了一个对象, new出来的对象依旧在堆区,但是把这个堆上的内存地址,赋值给了这个f变量)
二.JVM类加载的过程
运行java进程的时候,JVM就需要读取 .class中的内容,并且执行里面的指令(读取 .class中的内容就是类加载,把类涉及到的字节码从硬盘读取到内存中(元数据区),把这个 .class 中的指令转换成类对象)
加载一个 .class文件,就会对应创建一个类对象,类对象就包含了 .class文件中的各种信息(比如:类名,类的属性和方法,继承的父类,实现的接口等等······)也就说明类对象就是对象的说明书/蓝本
1.类加载的具体步骤
类加载分为五个环节,也可以分为三个环节,无论是那种都是对的,只是一些环节被归为一类而已,此处说的是五个环节
(1)加载
把 .class 文件找到,代码中先见到类的名字,然后进一步的找到对应的 .class 文件(涉及到一系列目录查找的过程)找到后,
打开并读取文件内容
(2)验证
验证读到的 .class 文件的数据是否正确与合法,在Java标准文档中,明确定义了 .class 文件的格式是怎么样的
u4:4个字节的unsigned int,u2:2个字节的unsigned short这是在Java中的,c++则不一定,c++会因为不同的操作系统和编译器导致l同类型的字节数不同。
magic:魔幻数字,在二进制文件中的开头,有若干个字节,设置一个固定的常数进去,通过这个常数,标识当前这个文件是什么样的文件
minor_version和major_version:确保编译和运行时的JDK版本一致
constant_pool_count:常量池
cp_info:其他结构体
access_flags,this_class,super_class:代表这个类是public还是其他的修饰词,后续代表的是类和父类
fields_count ,fields[fields_count]:代表类中的属性
后面的都代表类中的方法
(3)分配内存空间
根据刚才读取到的内容,确定出类对象需要的内存空间,再申请这样的内存空间,并且把内存空间中的所有内容,都初始化为0(Java创建一个内存空间,都会把这个内存空间全部设置为0,后续再进一步的初始化)
类加载执行到第三步时,此时a的值还为0
(4)解析
主要针对类中的字符串常量进行处理,解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。
直接引用:平时谈到的代码中的引用,里面也保存了变量的地址。
符号引用:字符串常量,已经在 .class 文件中。
“hello”字符串常量,已经在 .class 文件中了,但是没有地址这样的概念,此时地址的概念就是内存的地址,取而代之的是,类似于文件偏移量的概念。
符号引用和直接引用图解:
(5)初始化
针对类对象做最终的初始化操作,执行静态成员的赋值语句。
执行类中的静态代码块,针对父类也要进行加载,在这个环节也会触发对父类的进行加载(执行上述的五个环节)
2.双亲委派模型
(1)双亲委派模型介绍
是类加载五个步骤中,第一个步骤里面的一个环节,给定 类全限定名,找到对应的class文件位置。
类加载器JVM中,已经内置了一些加载器,完成上述的内加载过程
JVM默认有三个类加载器:
不是Java父类子类的继承关系,而是类加载器中有一个parent这样的引用指向父亲
(2)工作流程
图解:
这样做的核心目的:
防止用户自己写的类,把标准库的类给覆盖了
保证标准库的类,被加载的优先级是最高的
扩展库其次,第三方库的优先级最低
三.JVM的垃圾回收机制(GC)
1.需要回收的内存区域
JVM中的内存区域的程序计数器和栈是不需要GC的,因为它们会跟随线程一起销毁。而堆是GC主要回收的地方,堆上new的对象就需要GC判断是否需要回收。元数据区中的类对象的类加载是有上限的,不会出现无限增长的情况,一般不需要GC。
堆的分区
2.回收机制实现
(1)找出“垃圾”
需要针对每个对象进行判定是否为“垃圾”,在Java中使用一个对象,一般都是通过引用来使用的,如果一个对象没有引用指向了,都可以认为这个对象是“垃圾”了。
i.引用计数器
给每个对象分配一个计数器,衡量有多少个引用指向,每次增加一个引用,计数器+1,每次减少一个引用计数器-1,当计数器减为0时,此时对象就是“垃圾”了(多线程的可重入锁也是类似的机制)
图解计数器:
第一步
第二步
第三步
第四步
这样的方案在Java中并没有采纳,而是python和PHP进行了使用
上述方案存在两个问题:
1.消耗了额外的空间(假设Test类就只有一个int成员(4个字节),此时为了引入计数器,最少也要一个short(2个字节),内存就多占用了50%)
2.引入计数器可能导致“循环引用”使得上述的判定出错
第一步
第二步
第三步
这两个对象的引用计数都不是0,不能被释放,但是这两个对象又无法被使用,就造成了类似于死锁的场景。循环引用也是有解的,需要引入更多的机制(环路检测)代价就更大了。
ii.可达性分析(Java采用的方案)
在JVM中,专门搞了一些周期性的线程,扫描代码中所有的对象,判定某个对象是否是“可达”(可以被访问到),对应的,不可达的对象就是垃圾了。
1)JVM有一个所有对象的总名单
2)JVM针对当前教室里面的对象进行点名操作(被点到的,回答到),没有回答到的,就是缺课的(也就是“垃圾”)
通过类似于二叉树的结构,获取到根节点,就可以对整个树进行遍历,遍历就能够得到每一个节点的对象,此时这些对象都是可
达的。如果此时根节点的右节点为null,则为不可达。
类似于这样的树结构,当c不可达时,f也是不可达的,此时的f的可达性依赖于c的可达性。
可达性分析的起点称为GC root,一个程序中,GC root不是只有一个,而是有很多(栈上的局部变量(引用类型),方法去中,静态的成员变量(引用类型),常量池引用指向的对象,这三个GC root)
可达性分析,其实是挺消耗时间的,尤其是你的程序中,对象特别多的情况下,就需要很多时间。
(2)释放“垃圾”的内存空间
1.标记- 清除
直接针对内存中对应的对象进行释放
但是上述的做法会引入“内存碎片问题”,对象的释放是随机的,很可能释放的这个内存不是连续的,虽然把上述的内存释放掉了,但是整体这些空闲内存并没有连在一起,后续申请内存的时候,就无法申请(申请的内存必须是连续的)
2.复制算法
将一块内存分成两块,同一时刻,只使用其中的一半,当释放垃圾之前,把不是垃圾的对象拷贝到另一半中(确保拷贝的对象是连续的)然后把需要释放垃圾的这一半的空间都是释放了
第一步
第二步
但是复制算法的缺点也很明显:内存空间利用率太低了;如果存活下来的对象比较多,复制的成本也比较大。
3.标记-整理
非常类似于,顺序表删除中间的元素。
第一步
第二步
但是像这样进行搬运的开销也不小。
4.分代回收
JVM中真实的解决方案,是把上述几个方案综合一下,取长补短,也就转换成了分代回收
JVM根据对象的年龄(可达性分析,周期性的每次经过一轮扫描,对象仍然存活(不是垃圾)年龄+1),把对象进行区分
新创建的对象存储在伊甸区,根据经验规律,绝大部分的新对象,活不过第一轮GC,留存下来的对象,拷贝到幸存区。
幸存区是两个相等的空间,也是按照复制算法(反复进行多次在幸存者区的两个内存空间中来回进行复制,只要通过GC的考验,就能够存活下来)
新生代中,真正要拷贝的对象不多(经验规律),但是内存利用率低(幸存区中的另一个内存就会被浪费,但是幸存区的内存空间比较小,所以没关系)
如果一个对象在幸存区,已经反复被拷贝多次,也不是垃圾,年龄则会不断增长,达到一定程度后,对象就要被拷贝到老年代了。
根据经验规律,老年代中的对象的生命周期都会比较长,老年代的对象当然也需要进行可达性分析,但是老年代进行GC的频率就会降低,另外,老年代也是通过标记整理(需要整理的次数也不多)
3.其他的垃圾回收器
分代回收时JVM中GC的基本思想方法,具体落实到JVM的实现层上,JVM还提供了多种“垃圾回收器”(对上述的分代回收,做进一步的扩充和具体实现)
重点了解CMS和G1
CMS:
G1: