深入理解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虚拟机的主要内容,如果未能解决你的问题,请参考以下文章

《深入理解java虚拟机》-晚期(运行期)优化

jvm,深入理解java虚拟机,实战:OutOfMemoryError异常

重读《深入理解Java虚拟机》虚拟机如何加载Class文件

深入理解JAVA虚拟机 晚期(运行期)优化(转载)

深入理解java虚拟机

深入理解Java虚拟机:JVM高级特性与最佳实践的内容简介