JVM加强

目录

JVM运行时的数据区(内存结构):

线程独享:

线程共享:

什么时候会内存溢出

JVM有哪些垃圾回收算法

GC如何判断对象可以被回收

典型的垃圾回收器

CMS:

G1:

类加载器和双亲委派机制:

类加载器

双亲委派机制

JVM中有哪些引用

虚引用

类加载的过程

JVM类初始化的顺序

对象的创建过程

对象头中有哪些信息

JVM的内存参数

GC的回收机制与原理


JVM运行时的数据区(内存结构):

线程独享:

  • 虚拟机栈:每次调用方法时都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法对应的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧。
  • 本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一。
  • 程序计数器:保存指令执行的地址,方便线程切回后能够继续执行代码

线程共享:

  • 堆内存:jvm进行垃圾回收的主要区域,存放对象相关信息,分为了新生代和老年代,内存比例一般为1:2,新生代的Eden区域内存不够的时候发生Minor GC,老年代内存不够的时候发生Full GC
  • 方法区:存放有关类的信息。存放类信息、静态变量、常量、运行时常量池等信息。JDK1.8之前用永久代实现,JDK1.8之后采用元空间实现,元空间使用的是本地内存,而非存放在JVM结构当中

什么时候会内存溢出

堆内存溢出:

  1. 对象一直创建而不被回收的时候
  2. 加载的类越来越多的时候
  3. 虚拟机栈的线程越来越多的时候(如果应用程序创建了过多的线程,并且每个线程都创建了大量的对象,堆内存的使用量就会增加。)

栈溢出:

  1. 方法调用次数太多,一般为递归调用不当造成

JVM有哪些垃圾回收算法

标记清除算法:标记不需要回收的对象(首先会进行标记阶段。从根对象(如全局变量、栈中的引用等)开始,通过可达性分析或其他方式,遍历对象图谱,将所有与根对象可达的对象标记为存活对象),然后清除没有标记的对象,这种方式会造成许多内存碎片。

复制算法:将内存将内存分为两块,只使用一块,进行垃圾回收的时候,先将存活的对象复杂到另一块区域,然后在清空之前的区域。(新生代中的from区和to区用的就是类似这种算法)

标记整理算法:与标记清除算法类似,但是在标记之后,将存活的对象向一方移动,然后清除边界外的垃圾对象。应用于老年代的垃圾回收

GC如何判断对象可以被回收

引用计数法:为每个对象添加引用计数器,当引用个数为0的时候判定为可以被回收,但存在有两个对象相互引用导致无法回收的问题

可达性分析算法:从GCRoots开始向下搜索,搜索过的路径称为引用链,对于GCRoots没有任何引用的对象,则判定可以被回收

什么是GCRoots:

在Java中,以下几种情况可以被定义为GCRoots:

  1. 栈中的引用:包括局部变量、方法参数和操作数栈中的引用。
  2. 静态变量:属于类的静态字段,因为它们始终存在于内存中。
  3. JNI(Java Native Interface)引用:JNI是Java与本地代码交互的机制,JNI引用指向在本地代码中创建的Java对象。
  4. 虚拟机内部的引用:包括虚拟机常量引用和系统类加载器等特殊类的引用。
  5. 线程相关引用:例如当前活动线程的引用或线程的任务队列中的引用。

典型的垃圾回收器

CMS:

以最小的停顿时间为目标,只运行在老年代的垃圾回收器,使用标记清除算法,可以并发收集(标记和清除阶段)

CMS回收器采用了一种并发标记清除算法,具有以下几个主要特点:

  1. 并发:CMS回收器的标记和清除阶段是与应用程序线程并发执行的。它尽量减少垃圾收集期间的停顿时间,以提供更好的响应性能。在标记阶段,由于与应用程序线程同时运行,可以减少暂停时间。
  2. 低停顿:CMS回收器的主要目标是减少应用程序的停顿时间。通过在标记和清除阶段的并发执行,可以避免全局停顿。但为了达到低停顿的效果,CMS回收器会牺牲一部分吞吐量。
  3. 分代收集:CMS回收器主要关注老年代的回收,因为老年代通常包含更多的存活对象。对于新生代,CMS回收器通常与其他回收器(如Serial或ParNew)结合使用。
  4. 标记-清除算法:CMS回收器采用标记-清除算法,首先进行并发标记,然后进行并发清除。标记过程中使用“标记”位来标记存活对象,而清除阶段会清除所有未被标记的对象,释放内存空间。
  5. 空间碎片:CMS回收器在并发清除阶段不会移动对象,这可能导致内存碎片的产生。为了解决内存碎片问题,CMS回收器提供了“空闲列表”来尽量利用连续的内存空间。

        需要注意的是,CMS回收器适用于具有大内存、长时间运行的应用程序,并且强调减少停顿时间。但由于并发执行和空间碎片的特点,它可能会导致一些额外的CPU开销,并且在极端情况下可能会触发Full GC。

G1:

JDK1.9之后的默认的垃圾回收器,注重响应速度,支持并发,采用标记整理+复制算法回收内存,使用可达性分析判断对象是否应该被回收。

G1回收器具有以下几个主要特点:

  1. 区域化的堆布局:与传统的分代堆模型不同,G1将堆划分为多个大小相等的区域。每个区域可以是Eden、Survivor、Old或Huge Region之一。这种区域化的布局有助于对垃圾回收的控制和优化。
  2. 并发标记:G1回收器使用并发标记算法来标记存活对象。在标记阶段,应用程序线程与垃圾收集线程并发执行,从而减少了停顿时间。
  3. 多线程并行处理:G1回收器在标记和清除阶段使用多个线程来并行处理垃圾回收任务,以提高回收效率。
  4. 基于回收价值的优先级:G1回收器根据每个区域内的回收价值来选择优先回收的区域,这样可以优先回收那些包含大量垃圾的区域,从而提高回收效率。
  5. 整理空闲:G1回收器在执行垃圾回收时会进行部分或完全的内存整理,以减少内存碎片,提高内存使用效率。
  6. 可预测的停顿时间:G1回收器通过将回收任务划分为多个短暂的回收周期来控制停顿时间。用户可以通过配置参数来指定期望的最大停顿时间,G1回收器会尽量在这个时间范围内完成垃圾回收。

        G1回收器适用于具有大内存、需要低延迟和可预测停顿时间的应用程序。相对于CMS回收器,G1回收器提供了更好的吞吐量和停顿时间控制。但与此同时,它也引入了一些额外的开销,如内存开销、CPU开销和GC负载。

类加载器和双亲委派机制:

类加载器

        类加载器负责从文件系统、网络或其他来源加载类的字节码,并将其转换为JVM可以理解的格式。JVM支持多个类加载器,每个加载器都负责加载特定类型的类。

从父加载器到子类加载器分别为:

BootStrapClassLoader(启动类加载器)         加载路径——JAVA_HOME/jre/lib

ExtensionClassLoader(扩展类加载器)         加载路径——JAVA_HOME/jre/lib/ext

ApplicationClassLoader (应用程序类加载器)       加载路径——classPath

双亲委派机制

        双亲委派机制是类加载器的一种工作方式,它通过层级关系进行加载类的过程。当一个类加载器需要加载一个类时,它首先将这个任务委派给它的父类加载器,父类加载器再将任务委派给它的父类加载器,直至最终委派给顶层的启动类加载器。只有当父类加载器无法加载这个类时,子加载器才会尝试自己加载。

优势

  1. 安全性: 假设有一个恶意代码想要替换Java核心API类中的某个类,比如java.lang.String。由于双亲委派机制的存在,该加载请求会首先被委派给启动类加载器进行处理。启动类加载器负责加载核心Java API,并将其保护起来,以防止被篡改或替换。因此,即使存在恶意代码尝试加载java.lang.String类,由于该类已由启动类加载器加载,恶意代码的加载请求将被拒绝。

  2. 避免重复加载: 假设我们有两个不同的类加载器,分别是ClassloaderAClassloaderB,它们都可以加载同一个类com.example.MyClass。如果没有双亲委派机制,当需要加载com.example.MyClass时,ClassloaderAClassloaderB可能会分别加载自己的版本,导致在JVM中存在多个com.example.MyClass类的副本,可能会引发类冲突和不一致的问题。而通过双亲委派机制,当ClassloaderAClassloaderB都遵循委派机制,它们都会将加载请求委派给父类加载器,最终由启动类加载器加载该类。这样,只有一个版本的com.example.MyClass类会被加载,避免了重复加载和冲突。

  3. 存在性检查: 当一个类加载器需要加载某个类时,它会先询问父类加载器是否已经加载过该类。如果父类加载器已经加载了该类,那么子类加载器就无需再次加载,直接使用父类加载器加载的类。这样做可以提高加载效率,避免不必要的重复加载操作。例如,当一个应用程序使用Class.forName("com.example.MyClass")动态加载某个类时,首先会询问应用程序类加载器(Application Class Loader)是否已经加载了该类。如果已经加载,则可以直接使用,而不需要再次加载。

JVM中有哪些引用

        在JVM(Java虚拟机)中,引用是指对对象的间接访问。引用允许我们操作和处理对象,而无需直接访问对象本身。引用是一种对内存中对象的标识,通过引用可以定位和操作存储在堆中的对象。

        引用在Java中有不同的类型,包括强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。它们的主要区别在于对垃圾回收的影响和生命周期的长短。

  1. 强引用:new的对象。哪怕内存溢出也不会回收
  2. 软引用:只有内存不足时才会回收
  3. 弱引用:每次垃圾回收都会回收
  4. 虚引用:必须配合引用队列使用,一般用于追踪垃圾回收动作、

虚引用

当一个对象只有虚引用指向它时,无论何时垃圾回收器决定回收该对象,它都可以立即进行回收。在回收对象之前,垃圾回收器会将这个对象放入一个特殊的引用队列中。

使用虚引用的好处是,我们可以通过检查引用队列来确定哪些对象已经被回收。这样,我们可以在对象被回收时采取一些额外的操作或记录。例如,我们可以在对象被回收时进行资源清理或日志记录。

要注意的是,虚引用本身并不能保持对象的存活,也不能通过虚引用来访问对象。它仅仅是在对象被回收前提供了一个通知的机制,以便我们可以观察和处理对象的回收过程。

所以,虚引用的主要作用是帮助开发者了解对象的回收情况,并在需要时执行一些附加的操作。

 

类加载的过程

(1)加载:将字节码通过二进制的形式转化到方法区中的运行数据区当中。

(2)连接:

  • 验证:验证字节码文件的正确性
  • 准备:正式为类变量在方法区中分配内存,并设置默认的初始值,final类型的变量是在编译时就赋值
  • 解析:将常量池中的符号引用(如类的全限定名)解析为直接引用(类再实际内存中的地址)

(3)初始化:执行类构造器(不是常规的那种类的构造方法),为静态变量赋初值并初始化静态代码块。

JVM类初始化的顺序

父类静态代码块和静态成员变量-》

子类静态代码块和静态成员变量-》

父类代码块和普通成员变量-》

子类代码块和普通成员变量-》

子类构造方法

对象的创建过程

(1)检查类是否被加载,如果没加载就先加载类

(2)为对象在堆中分配内存,使用CAS方式分配,防止在为A进行内存分配的时候,执行当前地址的指针还没有来得及修改,对象B就拿来分配内存(让A分配完了再让B分配)

(3)初始化,将对象中的属性都分配为0值或者为null

(4)设置对象头

(5)为属性赋值和执行构造方法

对象头中有哪些信息

1.Markword

  • 锁信息
  • hashcode
  • GC标记

2.类指针KlassPointer

JVM的内存参数

-Xmx[]:堆空间最大内存

-Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的

-Xmn[]:新生代的最大内存

-xx:[survivorRatio=3]:Eden区与from+to区的比例为3:1,默认为4:1

-xx[use 垃圾回收器名称]:指定垃圾回收器

-xss:设置单个线程栈大小

一般设堆空间为最大可用物理地址的百分之80
 

GC的回收机制与原理

        GC的目的实现内存的自动释放,使用可达性分析算法判断对象是否可被回收,采用了分代回收的思想,将堆分为了新生代、老年代;新生代中采取了复制算法,老年代中采用了标记整理算法,当新生代内存不够的时候发生Minor GC,老年代内存不够的时候发生Full GC

你可能感兴趣的:(jvm)