关于JVM的一些基础知识

关于JVM的一些基础知识

1.基础部分

1.1 JVM 是什么以及它的产品

jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的*
Oracle的HotSpot、IBM的J9、阿里的TaobaoVM

1.2 JVM的构成部分

1、类加载子系统:读取类到内存,校验、初始化
2、运行时数据区:存储类信息、对象信息、执行逻辑
3、执行引擎:读取数据,解释执行,GC操作
4、本地方法库:Java语言和其他语言的协同

1.3 结构图

2.关于类加载

2.1 类加载器有哪些

1、BootStrapClassLoader——根类加载器。加载基础类库中的类
2、ExecutionClassLoader——扩展类加载器。加载jdk自带的扩展类
3、ApplicationClassLoader——应用类加载器。加载自己写的类
4、XXXXClassLoader——自定义类加载器。以上都不满足加载需求,则自己创建。

##为什么需要多个类加载器?##
各个类加载器各司其职,同时按需加载。

2.2 什么是双亲委派模型

双亲委派模型的工作方式我们可以概括为向上询问,向下委派。放我们需要加载一个类时,会先询问类加载器的上级加载器,是否已经加载过此类;若已经加载过此类,则返回,若没有加载过,就继续向上询问;如果上级加载器可以完成对此类的加载,则直接加载,如果不能,则向下一级加载器委托。

2.2.1 优势?劣势?

优:此机制可以保证一个类只被加载一次,可以说是对资源的一种保护。例如:我们自己也写了一个java.lang.Object类,那么使用双亲委派则可以保证,加载器只加载官方的Object类。
劣:当一个JVM中有多个项目,不同项目中有相同的包名类名,那么只有一个项目中的类会被加载。而且会影响一些性能

2.2.2 打破双亲委派?

Tomcat就是一个典型的例子。他的加载器结构如下:

它的加载流程是:WebAppClassLoader->ExtClassLoader->BootstrapClassLoader。跳过AppClassLoader,保证同一个Web容器上的不同应用使用的类库相互隔离。
若以上步骤没有加载成功,则WebAppClassLoader自身调用findClass()方法进行加载,先从WEB-INF/classes加载,再从WEB-INF/lib中加载。

2.2.3 如何自定义一个ClassLoader

写一个类,继承ClassLoader,重写loadClass()方法,委派机制就存在于loadClass()方法中。使用findClass()方法根据名称位置加载.class文件,IO读取文件内容,使用defineClass()方法将字节码转换为Class对象。

考虑的内容:1.指定加载源头;2.保证类安全;3.打破双亲委派
使用时机:1.Tomacat的应用需要部署–热加载;2.spring的动态代理,代理类是一个在运行过程中生成的新的class,使用中才要加载到内存

2.3 类加载的基本步骤

根据指定路径+类的全名查找类 ---->根据字节输入流读取类,存到内存中,将类信息存到字节数组中 ---->校验分析字节数组中信息,初始化并将结构内容存到方法区---->生成.Class字节码对象

2.4 触发加载类的情况

1、显示加载(动态加载):传入类全限定名的加载(编译时类可以缺席的加载)----初始化static变量和代码,非static仅加载类不执行代码
loadClass()方法——执行加载过程
forName()方法——执行类的加载、链接、初始化
2、隐式加载(静态加载):不传入类全限定名的加载(编译时类不能缺席的加载)----编译时自动加载所有类
new对象,访问属性或方法

2.5 类加载时静态代码块一定执行吗?

不一定。静态代码块是否执行,取决于类加载时是否执行初始化。

2.6 类的初始化顺序

  1. 父类的静态变量、静态代码
  2. 子类的静态变量、静态代码
  3. 父类的变量、代码
  4. 父类构造器
  5. 子类的变量、代码
  6. 子类的构造器

2.7 静态初始化与非静态初始化

​ 静态初始化在加载类的时候初始化,非静态初始化在new实例对象的时候加载类

2.8关于类的主动加载和被动加载(类的初始化)

1、主动加载:访问类成员或方法触发。

  • 创建类实例
  • 访问或赋值某个类或接口的静态变量
  • 调用类的静态方法
  • 反射
  • 初始化类时发现父类没有初始化,先初始化父类
  • 虚拟机启动时启动的类

2、被动加载:访问本类的父类属性,本类属于被动加载,不触发当前类的初始化;访问常量;数组定义引用类

2.7 内存中一个类的字节码对象可以有多个吗?

可以。类的字节码对象是否相同取决于——类名包名完全一致、ClassLoader一致

3. JVM运行内存

3.1 JVM运行内存的划分

其中方法区和堆是线程共享的;虚拟机栈、本地方法栈、程序计数器是线程私有的。
程序计数器:记录程序执行时的字节码指令地址

3.2 JVM虚拟机栈的结构

方法的执行对应入栈Push和出栈Pop的操作

3.2.1局部变量表

底层是数组。对于main方法,args变量存在下标0位置;对于实例方法,this存在下标0的位置
方法结束就销毁

3.2.2 操作数栈

核心作用是计算。程序计数器中有指令地址,通过地址找到指令。执行时:
将指令对应的数据放在操作数栈中;
将数据从操作数栈中取出放在局部变量表中;
将数据从局部变量表中取出,计算后,结果存在操作数栈中。

3.3 JVM堆结构

3.3.1 结构示意图

3.3.2 Java对象分配内存的过程
  1. 编译器逃逸分析,得到对象在哪儿分配。栈还是堆,没有逃逸,分配在栈上则无需回收
  2. 在堆分配:检测是否可以在TLAB(Thread-local-allocation buffer)上直接分配
  3. TLAB上无法直接分配,在Eden加锁区(CAS算法加锁)分配(线程共享区)
  4. Eden无法存储对象,执行Yong GC
  5. Yong GC后还无法分配,放在老年区

什么是TLAB
是一块线程私有的区域,无锁开销,存在于Eden区,默认大小为1%。因为Java程序中有很多短小对象,不存在线程共享且会被快速GC,那么这些对象就分配在TLAB上。
TLAB结合bump-the-pointer、TLABs
bump-the-pointer跟踪Eden区创建的最后一个对象,判断后面的剩余空间。如果有,创建,并放在顶部。
TLABs针对多线程的解决方案。将Eden区分为多段,每个线程独立使用一段,不需要锁进行同步,分配对象不需要锁住整个堆。

3.3.2.1 逃逸分析——一种全局数据流分析算法(-XX:DoEscapeAnalysis)
  • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
  • 当一个对象在方法被定义后,它被外部方法所引用,则认为发生逃逸,例如作为参数传递到其他方法中,或者作为返回值返回
  • 方法逃逸:一个对象在方法中定义后,作为参数传到其他方法中
  • 线程逃逸:类变量或实例变量,可能被其他线程访问到
3.3.2.2 同步消除、标量替换和栈上分配

1.同步消除:如果一个对象不会被其他线程访问到,那这个对象的读写就不存在竞争,则消除此对象的同步锁。-XX:+EliminateLocks
2.标量替换:标量指不可再分割的量,比如基本类型。如果一个对象不会被外部访问,则拆散对象,那么就不再生成该对象,而是在栈上创建若干成员变量,则GC次数减少,则正常业务执行效率提高。
-XX:+EliminateAllocations开启 -XX:+PrintEliminateAllocations查看
3.栈上分配:实际上是标量替换。

3.3.2.3 编译阈值

即时编译JIT只在代码段执行足够次数才会进行优化 -XX:CompileThreshold设置

3.3.3 内存泄漏和内存溢出
3.3.3.1 内存泄漏

程序申请内存空间,但是无法释放已经申请的内存空间,系统无法及时回收并分配给其他程序。

  • 大量使用静态变量(静态变量的声明周期和程序一样)
  • IO用完没有关闭(没有进行close操作)
  • 使用强引用缓存(不断在集合中放入对象)
  • ThreadLocal 应用不当(用完记得执行 remove 操作)
3.3.3.2 内存溢出:

**程序申请内存空间,没有足够的内存,数据无法正常存储。**OutOfMemoryError

  • 创建的对象太大,太多(不能及时回收)
  • 方法出现无限递归(不断入栈)
  • 实例内部类
  • 大量内存泄漏
  • 方法区内存空间不足,不断加载新的类
3.3.4 如何确定垃圾对象——引用计数法、可达性分析
3.3.4.1 引用计数法

一个引用指向对象,计数+1,若为0,回收。 无法解决循环引用——A有成员属性,类型是B,B有成员属性,类型是A;给两个对象的成员属性赋值为null,A和B又存在引用关系,那永远不会被回收。

3.3.4.2 可达性分析——三色标记法

以GC Roots为起点,向下搜索,走过的路径称为“引用链”。若一个对象到GC Roots没有引用链,则对象不可用。
白色:没有被GC访问的对象,若全部标记完成,还是白色,称之为不可达对象
黑色:已经被GC访问,且子引用对象也被访问
灰色:已经被GC访问,子引用对象没有被访问

一旦发生GC垃圾回收,会先去判断对象是否执行了finalize方法
执行finalize方法—>将当前对象和GCRoot对象关联—>判断对象是否可达

GC Roots有哪些?(通过实例变量、类变量、局部变量可以直接访问的对象)

  • Java栈中引用的对象
  • 方法区中的常量和静态变量
  • 线程对象
  • 类加载器加载的对象
3.3.5 最大堆(-Xmx)和初始堆(-Xms)的大小为什么推荐设置为一样的

避免程序运行过程中,因对象多少或 GC 后内存发生了变化而调整堆大小,带来的更大系统开销

3.3.6 什么情况下对象会被存在老年代

1.创建的对象大,年轻代放不下
2.多次GC,生命计数器为15的对象

3.4 JVM方法区

3.4.1 JVM 方法区的构成

JDK8 的 HotSport 虚拟机中称之为 Metaspace(元空间)

3.4.2 JVM 方法区存储内容

存储被加载的类信息、常量、静态变量、即时编译后的代码等数据

3.4.3 Hotsport 虚拟机的方法区内存在哪、如何设置

JVM 堆外内存,属于操作系统的一部分内存。
‐ XX:MetaspaceSize=256M ‐ XX:MaxMetaspaceSize=256M 方式设置元空间的内存大小

3.5 JVM四种引用类型

3.5.1 强引用
Person p = new Person();
p = null;

强引用指向的对象不会被回收,内存不足抛出OutOfMemoryError,将其赋值为null可以中断强引用和对象的联系。

3.5.2 软引用
Person p = new Person();
SoftReference<Person> softRef = new SoftReference<Person>(p);//创建一个软引用指向对象,此时有两个引用指定Person对象
/*
1.将强引用指向null,此时有一个软引用指向Person对象
2.softRef是指向SoftReference的强引用
*/
p = null;
p = softRef.get();//重新得到Person对象,并用一个强引用指向它

软引用指向的对象在JVM内存不够的情况下才会被回收

3.5.3 弱引用
Person p = new Person();
WeakReference<Person> weakRef = new WeakReference<Person>(p);//创建一个弱引用指向对象,此时有两个引用指定Person对象
/*
1.将强引用指向null,此时有一个弱引用指向Person对象
*/
p = null;

弱引用可以被随时回收。
jdk也提供了java.util.WeakHashMap这么一个key为弱引用的Map

3.5.4虚引用
PhantomReference<Person> phantomRef = new PhantomReference<>(p, QUEUE);
//PhantomReference 类中只有一个方法 get(),返回值null

一个对象只有虚引用,等同于没有引用。
主要用来跟踪对象被垃圾回收器回收的活动
虚引用必须和引用队列一起使用。当该引用不可达时,GC会向该引用队列内塞入这个虚引用,Reference类创建一个线程处理这些被标记的虚引用

虚引用拓展——自动清理资源的两种方式

1.实现AutoCloseable接口(try-with-resource)

try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
       BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
      int b;
      while ((b = bin.read()) != -1) {
        bout.write(b);
      }
    }
    catch (IOException e) {
      e.printStackTrace();
    }

2.使用Cleaner和PhantomReference机制

3.6 项目哪些位置用到缓存

  • 数据库内置缓存(mysql的查询缓存)
  • 数据层缓存(mybatis)
  • 业务层缓存(基于map实现的缓存,分布式缓存–redis)
  • 浏览器内置缓存
  • CPU缓存

3.7 设计缓存考虑的问题

  • 存储结构(使用什么结构存储数据效率会更高?-散列表)
  • 淘汰算法(缓存容量有限-LRU/FIFO/LFU)
  • 任务调度(定期刷新缓存,缓存失效时间)
  • 并发安全(缓存并发访问时的线程安全)
  • 日志记录(缓存是否命中,命中率是多少)
  • 序列化(存对象时序列化、取对象时反序列化)

4. 字节码增强部分

4.1 字节码增强的应用

字节码增强:类加载或类运行时对类的字节码进行修改或生成新的字节码
AOP的应用。
字节码增强技术:ASM 技术、Javassist 技术

5. GC

5.1 GC算法

5.1.1 标记清除

首先扫描内存,对存在引用的对象标记;再次扫描内存,清除没有标记的对象。会产生大量碎片,扫描两次效率低,用于老年代。

5.1.2 标记复制

首先扫描内存,对存在引用的对象标记;将标记的对象复制到一块内存空间,释放原来的空间。不会产生碎片,牺牲一定空间,适用于对象较少的区域——年轻代,因为活着的对象少,复制会快一点。

5.1.3 标记整理

首先扫描内存,找到活着的对象,将这些对象移动到一侧,清理另一侧空间。适用于老年代

5.2 JVM常见垃圾回收器

  • 串行(Serial):只有一个线程垃圾回收

  • 并行(Parallel):多个线程垃圾回收

  • 并发(CMS):GC执行垃圾回收时,可以有多个业务线程执行-----三色标记法

  • G1(收集器):将原有JVM物理内存连续的分代逻辑打散成逻辑上内存连续的分代逻辑
  • 查看JVM默认的垃圾收集器:-XX:+PrintCommandLineFlags -version

5.3 JVM常用配置参数

5.3.1 堆栈相关配置
  • -Xms:初始堆大小
  • -Xmx:最大堆大小
  • -Xmn:年轻代大小
  • -Xss:每个线程堆栈大小
  • -XX:NewRatio=4:设置年轻代和老年代的比值
  • -XX:SurvivorRatio=4:年轻代中Eden区和Survivor比值为4
  • -XX:MaxTenuringThreshold=0:垃圾最大年龄
  • -XX:+TraceClassLoading:跟踪类加载顺序
5.3.2 垃圾收集器相关配置
  • -XX:+UseParallelGC: 选择垃圾收集器为并行收集器
  • -XX:ParallelGCThreads=20: 配置并行收集器的线程数
  • -XX:+UseConcMarkSweepGC: 设置老年代为并发收集。
  • -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次 GC 以后对内存空间进行压缩、整理。
  • -XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

5.4 服务频繁fullGC,yongGC次数较少,可能原因

  • 经常有大对象进入老年代。-XX:PretenureSizeThreshold 设置,大于这个值的参数直接在老年代分配
  • 老年代参数设置不当。阈值到多少才进行一次CMS垃圾回收?
  • FULLGC后没有对老年代的碎片整理。用-XX:CMSFullGCsBeforeCompaction设置
  • 新生代小,两区比例不当
  • 内存泄漏
  • 元空间(方法区)不够。

5.5 JVM小工具

  • Jps:来输出 JVM 中运行的进程状态信息
  • Jstack:查看某个 Java 进程内的线程堆栈信息
  • Jmap:导出堆内存,然后使用 jhat 来进行分析
  • Jstat:统计监测工具,看看各个区内存和 GC 的情况

6. 缓存淘汰

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值