目录
1.JVM 简介
2. JVM 运行时数据区
2.1程序计数器
2.栈
3.堆
4.方法区
3.类加载
1.loading
2.linking
1.验证
2.准备
3.解析
3.Initializing
4.双亲委派模型
5.JVM垃圾回收机制
1.劣势
2.回收什么
3.垃圾回收具体怎么回收
1.找垃圾
方法:
问题:
2.释放垃圾
1.JVM 简介
JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机。
2. JVM 运行时数据区
2.1程序计数器
每个线程都有一个
1.作用:记录当前线程执行的行数(相当于一个书签)
内存中最小的区域,保存下一条要执行的指令的地址在哪里.
程序要运行,JVM要把字节码加载起来,放在内存中,程序就会一条一条把指令从内存中放到CPU内存上执行,就要记住当前执行的是哪一条了
2.栈
1.存储内容:局部变量和方法调用信息
一个进程只有一份
方法调用的时候,每次调用一个新的方法,就涉及到"入栈"操作,每次执行完方法,就涉及到"出栈"操作
栈空间很小,如果递归条件没有写好,可能会出现栈溢出
3.堆
一个进程有一份,多个线程共用一个堆
1.内存中空间最大的区域
2.存储内容:new的对象,对象的成员变量(不包含静态变量)
4.方法区
1.存储内容:类对象(包含静态成员)
.java->.class(二进制字节码).class会被加载到内存中,被JVM构造成类对象
3.类加载
把.class文件,加载到内存中,构成类对象
1.loading
找到对应的.class文件,打开并读取.class文件,同时初步生成一个类对象
2.linking
建立多个实体直接的联系
1.验证
验证读到的内容是不是和规范中规定的格式,完全匹配
如果读到的数据格式不符合规范,就会;类加载,并抛出异常
2.准备
为类中的变量(静态变量)分配内存并设置类变量初始值
3.解析
java虚拟机把常量池中的符号引用替换成直接引用的过程,是初始化常量的过程
.class文件的结构体中初识情况下是记录了编号,根据编号找到对应内容,填充到类对象中
3.Initializing
真正对类对象进行初始化,尤其是针对静态成员
4.双亲委派模型
JVN中类加载器,根据类的全限定名找到.class文件的过程
默认的类加载器,主要是3个~~
1.BootStrapClassLoader负责加载标准库中的类(String, ArrayList, Random, Scanner.....)
2.ExtensionClassLoader负责加载JDK扩展的类.
3.ApplicationClassLoader 负责加载当前项目目录中的类
双亲委派模型:描述了找目录的过程,就是上述类加载器是如何配合的
1)考虑加载 java.lang.String
1>程序启动,进入AppliactionClassLoader类加载器
2>ApplicationClassLoader就会检查下,它的父加载器是否已经加载过了.如果没有,就调用父类加载器ExtensionClassLoader
3>ExtensionClassLoader也会检查下,它的父加载器是否加载过了.如果没有,就调用父类加载器BootStrapClassLoader
4>BootStrapClassLoader也会检查下,它的父加载器是否加载过,自己没有父亲,于是自己扫描自己负责的目录
5>java.lang.String这个类在标准库中能找到!!!直接由BootStrapClassLoader负责后续的加载过程.查找环节就结束了
2)考虑加载自己写的Test类
1>程序启动,进入AppliactionClassLoader类加载器
2>ApplicationClassLoader就会检查下,它的父加载器是否已经加载过了.如果没有,就调用父类加载器ExtensionClassLoader
3>ExtensionClassLoader也会检查下,它的父加载器是否加载过了.如果没有,就调用父类加载器BootStrapClassLoader
4>BootStrapClassLoader也会检查下,它的父加载器是否加载过,自己没有父亲,于是自己扫描自己负责的目录,没扫描到!回到子加载器继续扫描
5>ExtensionClassLoader也扫描自己负责的目录,也没扫描到,回到子加载器继续扫描.
6>ApplicationClassLoader也扫描自己负责的目录,能找到Test类,于是进行后续加载.查找目录的环节结束.
如果最终ApplicationClassLoader 也找不到,就会抛出 ClassNotFoundException 异常!!
5.JVM垃圾回收机制
1.劣势
1.影响额外的开销
2.影响程序的流畅运行
2.回收什么
1.程序计数器
固定大小,不涉及到释放,不需要GC
2.栈
函数执行完毕,对应的栈帧就自动释放
3.堆
最需要GC的,代码中大量的内存都在堆上
4.方法区
进行"类加载要释放内存,卸载操作很低频的操作
只释放完全不使用的
回收的基本单位是对象不是字节
3.垃圾回收具体怎么回收
1.找垃圾
方法:
1.基于引用计数
针对每个对象都有额外引入一小块内存,保存有对少个引用指向他
当内存不使用(引用计数为0),可以释放
例如:函数调用创建对象,分配内存,方法结束,局部变量和栈帧一起释放,使引用计数为0,就认为这个对象是个垃圾
缺陷:
1.空间利用率低,要是对象很小,空间利用率就降低
2.有循环引用的问题
两个对象t1,t2分别引用对方的属性,此时两个对象的引用计数为2,要是两个对象都设置为null,此时引用计数不为0,无法释放,因为引用长在彼此的身上,外界的代码无法访问到这两个对象,此时两个对象不能使用,不能释放,就会出现内存泄漏
2.基于可达性分析
通过额外的线程,定期对整个内存空间的对象进行扫描
有起始位置,把可以访问到的对象标记一遍,没有标记的就是不可达,就是垃圾
起始位置:
栈上的局部变量
常量池中的引用指向对象
方法区中的静态成员指向的对象
优点:克服引用计数的缺点:空间利用率低,循环使用
缺点:系统开销大,遍历一次很慢
问题:
2.释放垃圾
1.标记-清除
标记:可达性分析的过程
清除:释放内存
直接释放内存,分布是离散的,问题"内存碎片"
2.复制算法
把不是垃圾的拷贝到另一半,把原来 空间整体都释放
问题:
内存利用率低
保留对象多,释放对象少时,开销大
3.标记-整理
根据复制算法进行改进
把保留的对象拷贝到前面(直接覆盖要释放的对象),多余的删除
空间利用率高,没有解决复制/搬运的开销
JVM实现会把多种方案结合
分代回收
针对对象分类:(根据对象的年龄分类),一个对象熬过一轮GC的扫描,长一岁
不同年龄用不同的方案
1.刚创建的对象放在伊甸区
2.伊甸区对象熬过一轮GC扫描,被拷贝到幸存区(使用复制算法)
3.在后续的GC,幸存区对象在两个幸存区中来回拷贝(复制算法),每轮都会淘汰幸存者
4.持续若干轮后,进入老年的代
因此老年代的GC扫描频率大大低于新生代,老年代中使用标记整理的方式进行回收