总结之虚拟机篇


前言

本篇博客是对于JVM的重要知识点的总结。主要包括JVM内存结构、JVM垃圾回收、JVM类加载加载机制、四种引用等。


一、JVM内存结构

Java内存结构
结合一段Java代码理解内存划分:
 1.执行javac命令编译源代码得到字节码文件。
 2.执行java命令:
  (1)创建JVM,JVM申请必要内存,创建必要的执行引用和类加载器。
  (2)JVM调用加载子系统进行类加载,将类的相关信息放入方法区
  (3)创建main线程,使用虚拟机栈,开始执行main方法。
  (4)如果遇到了还没有加载的类,会继续出发类加载过程。
  (5)创建对象,使用来存储对象。
  (6)不在使用的对象,会由垃圾回收器在内存不足时回收。
  (7)调用方法,方法内的局部变量、方法参数、返回地址使用虚拟机栈中的栈帧存储,栈帧随着方法的调用而创建,随着方法的结束而销毁。
  (8)调用方法时,先到方法区获得该方法的字节码指令,由解释器将字节码指令解释为机器码执行。
  (9)调用方法,会将执行的指令行号读到程序计数器,当发生线程切换后,恢复时就可以从终端的位置继续执行。
  (10)对于非Java实现的本地方法调用,使用本地方法栈
  (11)对于热点代码或者频繁的循环使用的代码的调用,由 JIT即时编译器 将这些代码翻译成机器码缓存,提高执行性能。

运行时常量池:是方法区的一部分。Class字节码文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

方法区、永久代、元空间
 方法区:是JVM规范中定义的一块内存区域,用来存储类元数据、方法字节码、即时编译器需要的信息等。
 永久代:是Hotspot虚拟机对JVM规范的方法区d的Java8之前的实现。
 元空间:是Hotspot虚拟机对JVM规范的方法区的Java8之后的实现,将有的数据组织成对象存放到堆中,有些数据方法存储在本地内存中,这个本地内存叫做元空间。


二、类加载

1.触发类加载的情况

类加载是懒惰执行
 (1)使用一个类进行对象实例化时。
 (2)使用一个类时,如果此类的父类没有加载,先加载父类(类的继承、接口的实现/继承)。
 (3)使用静态属性或者静态方法。

2.类加载过程三阶段

 (1)加载(Loading)
 加载是懒惰执行。
  1.将类的字节码加载入方法区,创建类.class对象,作为方法区这个类的各种数据入口。
  2.如果此类的父类没有加载,先加载父类。
 (2)链接(Linking)
  1.验证:验证类是否符合Class规范,做合法性、安全性检查。确保Class文件的字节流中包含的信息是否符合当前虚拟机要求。
  2.准备:为static变量分配空间,设置默认值。
  3.解析:将常量池的符号引用解析为直接引用。
 (3)初始化(Initializing)
 初始化是懒惰执行。
  1.按照代码书写的顺序,为static修饰的变量赋值、static final 修饰的引用类型变量的赋值。(static final修饰的基本变量的赋值,在链接阶段已经完成了)
  2.执行静态构造代码块。

3.类加载器(ClassLoader)

 1.启动类加载器(Boostrap ClassLoader): 加载SE下的标准类。
 2.扩展类加载器(Extension ClassLoader): 加载SE下的扩展类。
 3.应用类加载器(Application ClassLoader): 自定义类、通过maven或其他工具引入的第三方类。

 查找类文件时启动、扩展类加载器根据固定位置去查找,应用类加载器,根据class path路径依次查找。

4.双亲委派机制

类加载器

双亲委派:指默认的三个类加载器之间遵循一个规范。需要加载类的时候,优先委派上机类加载器进行加载,如果上级类加载器
   ①可以找到这个类,由上级加载,加载后该类也对下级加载器可见。
   ②找不到这个类,则下级类加载器才有资格执行加载。

目的
  1.让上级类加载器中的类对下级个共享(反之不行),即能让自己写的类能依赖到jdk提供的核心类,保证不同的类加载器最终得到的都是同一个Object。
  2.让类的加载有优先次序,保证核心类优先加载,防止加载进来用户写好的恶意代码。


三、JVM垃圾回收

1.如何判断对象可以回收?

 (1)引用计数法:当一个对象引用为0时即可回收。
    缺点:循环引用(自身引用、成环的互相引用等)
 (2)可达性分析法:若一个对象直接或间接被跟对象(GC ROOTS)引用,则该对象不能被回收,否则该对象可以作为垃圾回收。
 可以被看做CS ROOT的对象:
    ①System class(JVM运行时的核心类)
    ②Native Stack(JVM在执行方法调用时必须调用的一些操作系统方法,操作系统方法执行时引用的Java对象)
    ③Busy Monitor(被加锁的对象不能被当成垃圾)
    ④Tread(活动线程使用的对象不能被当成垃圾)

2.Java的4中引用

 (1)强引用:沿着GC ROOT对象可以找到的对象。只有所有GC ROOT 对象都不通过强引用引用该对象时,该对象才会被回收。强引用是造成Java内存泄漏的主要原因之一。
 (2)软引用:仅有软引用引用该对象时,在垃圾回收后,内存不足时会再次触发垃圾回收,回收软引用对象。
 (3)弱引用:仅有弱引用引用该对象时,在垃圾回收时,无论内存是否重组,都会回收弱引用对象。
 (4)虚引用:必须配合引用队列使用,用于跟踪对象被垃圾回收的状态。

 引用队列:若通过软、弱引用间接引用的对象均被释放后,将软、弱引用加入引用队列。目的是对软弱引用对象自身做内存释放,需要使用引用队列遍历并释放,而避免它们自身原本是强引用而不能被回收。

3.垃圾回收算法

 (1)标记清除
  1.标记阶段:沿着GC ROOTS对象的引用链查找,对直接或间接引用到的对象加上标记。
  2.清除阶段:释放未加标记的对象占用的内存,并记录空闲空间的起始地址。
  优点:速度快。
  缺点:容易产生内存碎片。
 (2)标记整理
  1.标记阶段:沿着GC ROOTS对象的引用链查找,对直接或间接引用到的对象加上标记。
  2.整理阶段:在清除的过程中将被标记对象向一端移动,可以避免内存碎片的产生。
  优点:没有内存碎片。
  缺点:涉及存活对象的内存移动以及引用地址的变更,速度较慢。
 (3)复制
  1.将内存区域划分成大小相等的两个区域,from和to,其中to总是处于空闲,from存储新创建的对象。
  2.找出存活对象后,将它们从from区复制到to区,复制过程中自然完成了碎片整理。
  3.复制完成后,交换from和to的位置。
  优点:不会产生内存碎片。
  缺点:占用双倍的空间。

分代垃圾回收机制

实际中采用上述三种垃圾回收算法的组合,让它们协同工作。
现代JVM由于GC的性能问题,把对空间再次分区域分别进行归纳和管理。

将 堆内存 大致划分为: 新生代 + 老年代,根据对象生命周期的不同特点,进行不同的垃圾回收策略分代回收堆内存划分垃圾回收流程:
 (1)对象首先分配在伊甸园区域。
 (2)新生代伊甸园空间不足时,触发 Minor GC(标记复制)(会触发STW),将伊甸园和from中存活的对象复制到to中,存活的对象年龄+1,并交换from和to。
 (3)当对象寿命超过阈值(15(4bit))时,会晋升至老年代(幸存区内存不足或大对象会导致提前晋升)。
 (4)当老年代空间不足时,先尝试触发Major GC(标记整理),若之后内存空间仍然不足,触发Full GC,STW时间更长。
 (5)Full GC之后空间仍然不足,则触发outOfMemorryError:Java heap space.

GC规模
GC目的在于实现无用对象内存自动释放、减少内存碎片、加快分配速度。回收的区域是堆内存,不包括虚拟机栈。
Minor GC:发生在新生代的垃圾回收,STW短。由于Eden大部分对象都会死去,所以存活下来的对象很少,只需要使用复制就可以以很小的代价完成。
Major GC:新生代 + 老年代部分区域的垃圾回收。由于老年代的对象一般不多所以使用标记整理
Full GC:新生代 + 老年代完整垃圾回收,STW长,成本较大,需要尽量减少老年代GC。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值