JVM相关知识

JVM相关知识

1 JVM内存分哪几个区,每个区的作用

(1) 方法区
有时也叫做永久代在该区内很少发生垃圾回收,但是并不代表不发生GC,这里的GC主要是对方法区里的常量池和对类型的卸载。方法区和永久代的关系,相当于java中接口和类的关系,类实现了接口,方法区是java虚拟机规范的定义,永久代是一种实现。
方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
该区域是被线程共享的。
方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。

jdk1.8开始后,方法区被移除,用元空间代替。元空间使用的是直接内存。

那么为什么将永久代替换为元空间呢? 原因有其下:
<1> 永久代有jvm固定的大小上限,无法进行调整。 元空间使用直接内存,降低了OOM的风险。
<2> 元空间存放的是类的元数据信息,由系统可用的空间控制,那么可以加载的类信息更多

这里的直接内存并不是jvm运行时数据区的一部分,也不是虚拟机规范定义的内存区域。直接内存受本机内存大小的限制

(2) 虚拟机栈
**虚拟机栈也就是栈内存,它为java方法服务。**每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
虚拟机栈是线程私有的,它的生命周期与线程相同。
虚拟机栈描述java方法执行的内存模型。

(3)本地方法栈:
本地方法栈和虚拟机栈类似,本地方法栈为Native方法服务。方法执行完后,释放空间。

(4)堆:
java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。
堆最容易发生OOM错误。

(5)程序计数器:
内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。

五个部分中,线程共享的是堆和方法区。线程私有的是虚拟机栈,本地方法栈和程序计数器。

2 java类加载过程
(1)加载
加载时类加载的第一个过程,在这个阶段,将完成以下事情:
通过一个类的全限定名获取该类的二进制流。
将该二进制流中的静态存储结构转化为方法去运行时数据结构。
在内存中生成该类的Class对象,作为该类的数据访问入口。

(2)验证
验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下验证:
文件格式验证:
元数据验证
字节码验证
符号引用验证
准备
准备阶段是为类的静态变量分配内存并将其初始化为默认值

(3) 解析
该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

(4)初始化
初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

3 类加载器,类加载器有哪些
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:
(1)启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
(2)扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
(3)系统类加载器(system class loader)也叫应用类加载器:它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
(4)用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。

4 垃圾回收算法以及特点:
Java的垃圾回收算法是用于自动管理内存的机制,它负责回收不再使用的对象并释放其所占用的内存空间。Java虚拟机使用了多种垃圾回收算法来满足不同场景和需求。以下是几种常见的Java垃圾回收算法:

标记-清除算法(Mark and Sweep):这是最基本的垃圾回收算法。它通过标记所有可达对象,并清除那些未标记的对象。标记阶段遍历从根对象开始的引用链,标记可达对象,清除阶段回收未被标记的对象。标记-清除算法会产生内存碎片,导致内存利用率降低。

复制算法(Copying):复制算法将可用内存分为两个区域,一部分是活动对象的区域,另一部分是闲置的区域。它首先在活动对象区域进行垃圾回收,并将存活的对象复制到闲置区域,然后清除活动对象区域。复制算法解决了内存碎片问题,但需要额外的空间用于复制对象。

标记-整理算法(Mark and Compact)标记-整理算法首先标记所有可达对象,然后将存活的对象向一端移动,并清除边界以外的内存。标记-整理算法解决了内存碎片问题,并且不需要额外的空间,但需要额外的整理操作。

分代算法(Generational):分代算法是一种综合利用多种垃圾回收算法的策略。它将内存分为不同的代,一般分为新生代(Young Generation)和老年代(Old Generation)。新生代中的对象生命周期较短,使用复制算法进行垃圾回收而老年代中的对象生命周期较长,使用标记-整理算法进行垃圾回收。分代算法通过针对不同对象的特点进行优化,提高垃圾回收的效率。

5 常见的垃圾回收器以及特点
垃圾收集器包括Serial、parNew、ParallelScavenge、CMS、G1

Serial: 串行收集,一条垃圾收集线程去收集新生代复制算法 老年代标记整理。 简单高效

ParNew: 实际上是Serial的多线程版本,新生代复制算法 老年代: 标记整理

ParallelScavenge: 新生代: 复制算法 老年代: 标记整理 多线程收集器 关注的是吞吐量,CMS关注的是用户线程的停顿时间。Jdk1.8默认的收集器

CMS: HotSpot虚拟机第一款真正意义上的并发收集器,第一次实现了垃圾收集线程和用户线程同时工作,CMS基于标记清除算法。并发收集,低停顿。 缺点: 对CPU资源敏感,无法处理浮动垃圾,会产生大量的空间碎片

G1: 面向服务器的垃圾收集器,以极高概率满足GC停顿时间要求,具备高吞吐量性能。
特点: 并行与并发 分代收集 整体基于标记整理,局部基于复制 建立了可预测的停顿时间模型

6 如何判断一个对象是否存活
(1)引用计数法
给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回。
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收。

(2)可达性算法(引用链法)
通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。

在java中可以作为GC Roots的对象有以下几种:虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象。

7 强引用 软引用 弱引用 虚引用
<1> 强引用: 最普通的引用。 类比于生活的必需品。垃圾回收器不会回收它,当内存空间不足时,宁可抛出异常,也不随意进行回收。 使用最多

<2> 软引用: 类比于生活中可有可无的东西。内存足够,不回收,内存不够,就进行回收。它可以和引用队列联合使用。 可以加速JVM对垃圾回收的速度,维护系统运行安全,防止内存溢出等问题

<3> 弱引用: 类比于生活中可有可无的东西。如果只具备弱引用的对象,生命周期更短暂,垃圾回收器一旦发现只具备弱引用的对象,内存够和不够都要进行回收。同时也可以和引用队列联合使用。

<4> 虚引用: 虚引用不会决定对象的生命周期。一个具有虚引用的对象,在任何时候都可能被回收。虚引用主要是来跟踪对象被垃圾回收的活动,必须和引用队列来联合使用。

8 java内存分配策略
java堆是垃圾回收器管理的主要区域。java堆可细分为新生代和老年代,新生代可细分为Eden区和From 区 To区
对象优先在堆的Eden区分配。
大对象直接进入老年代。
长期存活的对象将直接进入老年代。

大对象指的是需要大量连续内存空间的对象,比如字符串 数组等。

当Eden区没有足够的空间进行分配时,虚拟机会执行一次Minor GC。Minor GC通常发生在新生代的Eden区,在这个区的对象生存期短,往往发生GC的频率较高,回收速度比较快。Full Gc/Major GC 发生在老年代,一般情况下,触发老年代GC的时候不会触发Minor GC,但是通过配置,可以在Full GC之前进行一次Minor GC这样可以加快老年代的回收速度。

对象诞生,新生代发生在Eden区,在进行minor gc过程中,如果依旧存活,移动到from区,变成Survivor,进行标记。当一个对象存活默认超过15次都没有被回收掉,就会进入老年代。

9 判断一个类是否是无用的类
<1> 该类的所有实例被回收
<2> 加载该类的类加载器被回收
<3> 该类对应的java.lang.class对象没有在任何地方被引用,无法通过反射访问该类。

10 JVM调优工具
常用调优工具分为两类,jdk自带监控工具:jconsole,第三方有:MAT(Memory Analyzer Tool)

jconsole是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控。

MAT,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗

11 JVM性能调优以及GC策略

对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。

关注Full GC,因为它会对整个堆进行整理,
导致Full GC一般原因有:
<1> 旧生代空间不足 (老年代包含旧生代和一部分持久代)
尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间。不要创建过大的对象及数组和避免直接在旧生代创建对象

<2> 永久代空间不足
增大永久代空间,避免太多静态对象
控制好新生代和旧生代的比例
System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制

调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现
如果各个部分比例设置不妥当,会带来不同程度的影响。

新生代设置过小
新生代GC次数非常频繁,增大系统消耗;
导致大对象直接进入旧生代,占据了旧生代剩余空间,触发Full GC

新生代设置过大
一是新生代设置过大会导致旧生代过小,频繁的垃圾回收会导致系统资源的浪费和性能下降;
新生代GC耗时大幅度增加, 一般说来新生代占整个堆1/3比较合适

Survivor设置过小
导致对象从eden直接到达旧生代,降低了在新生代的存活时间

Survivor设置过大
导致Eden过小,增加了GC频率。Survivor区设置过大,需要更长的时间才能填满Survivor区并触发垃圾回收

根据上述情况,总结一下。新生代过大过小都不合适,新生代过小,会频繁Minor GC,同时老年代就相对增大,老年代的垃圾回收(Major GC)通常比新生代的垃圾回收更昂贵,会产生较长的停顿时间。 新生代过大,新生代GC耗时大幅度增加,垃圾货收效率下降。

JVM提供两种较为简单的GC策略的设置方式
吞吐量优先
JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。可由-XX:GCTimeRatio=n来设置

暂停时间优先
JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。可由-XX:MaxGCPauseRatio=n来设置

这次先到这里吧,下期再见。

你可能感兴趣的:(java,jvm,java)