深入理解java虚拟机
Posted GeekDengShuo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解java虚拟机相关的知识,希望对你有一定的参考价值。
title: 深入理解Java虚拟机
date: 2020-05-14 10:58:24
tags: JVM,虚拟机
1.运行时数据区域
1.程序计数器
当前线程执行字节码的行号指示器
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法,则为空)
2.虚拟机栈
虚拟机栈描述的是java方法执行的内存模型:方法在执行的时候创建一个栈帧(Frame)
栈帧中存储着(局部变量,操作数栈,常量池引用)
3.本地方法栈
为虚拟机使用Native方法服务
4.Java堆
所有对象都在堆上分配,是垃圾收集的主要区域
主要的垃圾回收算法都是分代收集算法,针对不同类型的对象采取不同的垃圾回收算法
堆:
- 新生代(Young Generation)
- 老生代(Old Generation)
4.方法区
方法区的别名叫非堆(No-Heap),也可以称之为永久代
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,垃圾回收将其作为永久代进行回收
运行时常量池
运行时常量池是方法区的一部分,存放编译期生成的各种字面量和符号引用
从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中
原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。
2.GC垃圾回收
内存回收三问?
那些内存需要回收?
什么时候进行回收?
如何回收?
1.如何判断一个对象是否可以进行回收
引用计数法
可达性分析算法
2.垃圾收集算法
标记-清除
效率问题:标记和清除的效率都不太高
空间问题:清除后的内存空间不连续
复制
主要是解决标记-清除算法的效率问题
将内存划分为较大的Eden和Survivor空间 8:1
用于新生代的回收
标记-整理
解决空间问题
将存活的对象移动到一侧
用于老年代的回收
垃圾收集采用分代收集算法,java堆分成老年代和新生代,根据特点采用不同的收集算法
3.垃圾收集器
Serial,ParNew,Parallel Scavenge,Serial Old,Parallel Old,CMS,G1收集器
3.内存分配与回收策略
内存分配策略
1.对象优先Eden分配
Eden空间不够会发生一次Minor GC
2.大对象直接进入老年代
大对象需要连续的内存空间,避免在新生代Eden和Survivor之间大量内存复制
3.长期存活的对象进入老年代
制定年纪计数器,增加到一定年龄放入到老年代中
-XX:MaxTenuringThreshold ,用来定义年龄的阈值
4.动态对象年龄判定
并不是所有对象必须达到MaxTenuringThreshold才能晋升到老年代
当相同年龄的所有对象的总和大于Survivor的一半,这些对象可以直接进行老年代
5.空间分配担保
使用老年代的空间来进行担保MinorGC
老年代的最大可用的连续空间大于新生代的空间总和时,MinorGC是安全的
内存回收策略
先进行MinorGC,老年代的空间内存担保失败,则进行FullGC(MajorGC)
FullGC的触发条件
- 调用System.gc()
- 老年代的空间不足
- 空间分配内存担保失败
4.类加载机制
Class的类文件结构
Class文件是以8位字节为基础的二进制流
每个Class的头四个字节称为魔数:"0xCAFEBABE"
虚拟机的类加载机制:
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型
加载,连接,初始化和运行:
其中解析(Resolution)和初始化(Initialization)的顺序不一定,为了实现Java运行时绑定(动态绑定),可以在初始化阶段之后再开始
1.加载
这个加载只是类加载的一个过程,要注意区分
加载过程中的主要事项:
-
1.通过类全限定名来获取定义该类的二进制字节流
-
2.将该字节流表示的静态存储结构转换为方法区的运行时存储结构
-
3.从内存中生成一个代表该类的Class对象,作为方法区中该类各种数据的访问入口
2.验证
验证是连接阶段的第一步
验证的目的是,确保Class文件的字节流中包含的信息符合当前虚拟机的要求
是虚拟化保护自身安全一项重要工作
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
3.准备
准备阶段为类变量(static 修饰)分配内存并设置初始值,使用的是方法区的内存。
public static int value=123;
初始值一般是0,而不是123
public static final int value=123;
类变量是常量,那么它将初始化为表达式所定义的值而不是 0,上面value的初始值为123
4.解析
常量池的符号引用替换直接引用的过程
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
5.初始化
初始化阶段才真正开始执行类定义的java程序代码
初始化阶段是虚拟机执行<clinit>()
方法的过程,在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化 类变量和其它资源。
父类的<clinit>()
方法先执行,父类中定义的静态语句块的执行要优先于子类
5.类与类加载器
两个类相等=两个类本身相等+使用同一个类加载器进行加载(每个类加载器都有一个独立的类名称空间)
类加载器的分类:
从java虚拟机的角度:
-
1.启动类加载器
Bootstrap Classloader,使用C++实现,是虚拟机的一部分
-
2.所有其他类的加载器,使用Java实现,独立于虚拟机,继承自java.lang.ClassLoader
类加载器细分:
- 1.启动类加载器
- 2.拓展类加载器
- 3.应用程序类加载器,如果没有自定义类加载器,这个就是程序的默认加载器
- 4.自定义的类加载器
双亲委派模型
双亲委派模型就是类加载器的的层次关系
- 工作过程:一个类加载器首先将类加载请求转发到父类加载器,只有父类加载器无法完成时才尝试自己加载
- 好处:优先级的层次关系,基础类得到统一
以上是关于深入理解java虚拟机的主要内容,如果未能解决你的问题,请参考以下文章