Java虚拟机(一)类加载、内存区域、垃圾回收策略

JVM简介

虚拟机是一种抽象化的计算机,Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

字节码文件

字节码文件也就是经过编译之后的class文件。

执行流程简介

执行一个class文件,JVM中的类装载子系统首先会将class文件装载进入的内存区域(运行时数据区),再由执行引擎执行程序。

类加载机制

指的是将类的class文件的二进制数据读入到内存中,并将其放入到运行时数据区中的方法区内,并在堆区中创建一个对应的class对象。

类加载过程

  1. 加载:通过IO读入字节码文件;
  2. 连接:执行校验、准备、解析的步骤;
  3. 初始化:对类的静态变量进行初始化成指定的值,以及执行静态代码块;
  4. 使用;
  5. 卸载

类的初始化和卸载

初始化

类的初始化,包括生成对象的初始化和类的静态块的实例化。

初始化触发的时机: 类被直接引用(主动引用)的时候。

主动引用

主动引用的情形有:

  1. 使用new关健字实例化对象

  2. 使用类的静态变量

  3. 使用类的静态方法

  4. 使用反射机制调用上述操作

  5. 程序入口 (调用main方法)

初始化顺序是:

静态块 -> 非静态块 -> 构造函数。

如果有父类,则初始化顺序是:
父类静态块 -> 子类静态块 -> 父类非静态块 -> 父类构造函数 -> 子类非静态块 -> 子类构造函数

被动引用

被动引用不会触发类的初始化。

被动引用的情形:

  1. 引用父类的静态字段,只会初始化父类,不会初始化子类。
  2. 引用类的常量,不会引起类的初始化。

卸载

在类使用完之后,满足下面的情形,会被卸载:

  • 该类在堆中的所有实例都已被回收,即在堆中不存在该类的实例对象。
  • 加载该类的classLoader已经被回收。
  • 该类对应的Class对象没有任何地方可以被引用,通过反射访问不到该Class对象。
    如果类满足卸载条件,JVM就在GC的时候,对类进行卸载,即在方法区清除类的信息。

类加载器的种类

  • 启动类加载器:加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsetsjar等;
  • 扩展类加载器:加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包;
  • 应用程序类加载器:加载ClassPath路径下的类包,主要就是加载自己写的那些类;
    自定义加载器:加载用户自定义路径下的类包。

运行时内存区域

分为两大类:线程私有区域、线程共有区域

线程私有区域:

  • 程序计数器:吉利字节码执行的行号;
  • java虚拟机栈:执行方法的时候,方法形成栈帧进行压栈,方法执行完毕时,栈帧出栈;
    栈帧:包含局部变量表、操作数栈、方法出口等;
  • 本地方法栈:调用native方法时产生的栈。

线程共有区域:

  • 堆内存: 分配所有对象实例的地方,也是垃圾回收的主要区域,也是内存分配最大的区域;
  • 元数据区(方法区):虚拟机啊加载的类信息,常量,静态变量;
  • 直接内存:非虚拟机内存,物理机的直接内存。

内存分配和垃圾回收策略

堆内存划分为年轻代,老年代。年轻代划分为eden区和survivor区,survivor区分为from区和to区;

对象优先在Eden区分配

大多数情况下,对象在eden区分配,eden区没有足够内存时,触发minorGC/YoungGC;

大对象直接进入老年代

可通过配置-XX:PretenureSizeThreshold来设置直接进入老年代的阈值,这样可以避免大对象在Eden区创建,因为大对象比较大,会造成minorGC的效率下降。

长期存活的对象将进入老年代

虚拟机给每个对象一个年龄计数器,Eden区触发minorGC,存活对象复制到from区,此时对象年龄设为1,之后的每一次熬过一次minorGC,年龄都会加1,当年龄增加到一定程度(默认15,可通过XX:MaxTenuringThreshold=数字 参数可以设置对象在经过多少次minorGC后会被放入老年代)

minorGC后存活的对象Survivor区放不下

这种情况时,JVM会将部分对象放入老年代,部分对象放入survivor区

Eden区和Survivor区内存分配比例

默认8:1:1

对象动态年龄判断

survivor区的一批对象的总大小大于这块survivor区总大小的50%,大于等于这批对象中最大年龄的对象的对象,就能直接进入老年代。
这篇博客写到挺详细:
https://www.jianshu.com/p/989d3b06a49d

老年代空间分配担保机制

JVM如何判断对象是否存活

  1. 引用计数法: 当对象引用计数器为0时,即无其他对象引用,判定为死亡,可被回收,但是存在循环引用问题,导致对象一直存活
  2. 可达性分析法:从GC root根向下搜索直接或间接可达的对象,判定为存活
原文地址:https://www.cnblogs.com/qiutianyou/p/15350564.html