JVM 见解

JVM内存模型变化

1. JDK 1.8之前的版本

在JDK 1.8之前的版本中,JVM内存模型主要包括以下几个区域:

  • 堆(Heap):JVM所管理的内存中最大的一块区域,也是被各个线程共享的内存区域。堆内存主要用于存放对象实例及数组。堆被进一步划分为新生代(Young Generation)和老年代(Old Generation),新生代又被细分为Eden区、Survivor0(From Space)和Survivor1(To Space)三个区域。
  • 方法区(Method Area):在JDK 1.8之前,也被称为永久代(PermGen space)。该区域用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 虚拟机栈(VM Stack):每个线程在执行Java方法时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  • 本地方法栈(Native Method Stack):与虚拟机栈类似,但它是为执行本地(Native)方法服务的。
  • 程序计数器(Program Counter Register):是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

2. JDK 1.8及之后的版本

从JDK 1.8开始,JVM内存模型的主要变化包括:

  • 永久代的移除与元空间的引入:永久代被移除,取而代之的是元空间(Metaspace)。元空间使用本地内存,而不是JVM的堆内存,这解决了永久代内存大小限制的问题。元空间主要用于存储类的元数据信息、常量池等。
  • 堆内存区域的命名:虽然堆内存区域的划分(新生代、老年代等)在JDK 1.8及之后的版本中保持不变,但相关的术语和概念可能更加清晰和统一。
  • 垃圾回收算法的改进:随着JDK版本的更新,JVM的垃圾回收算法也在不断优化和改进。例如,引入了并行垃圾回收器(Parallel GC)、并发标记清除垃圾回收器(CMS GC)以及G1(Garbage-First)垃圾回收器等,这些算法旨在提高垃圾回收的效率和性能

JVM1.8之前内存模型 

类的声明周期

     两种描述

                   加载-->连接-->初始化-->使用-->卸载

                    加载-->验证-->准备-->解析-->初始化-->使用-->卸载

  1. 加载(Loading):JVM通过类加载器(ClassLoader)将类的二进制数据从.class文件或其他来源(如网络)加载到JVM的内存中,并生成一个代表该类的java.lang.Class对象。

  2. 链接(Linking)

    • 验证(Verification):检查加载的类信息是否合法,确保不会危害JVM的安全。
    • 准备(Preparation):为类变量(静态变量)分配内存并设置初始默认值(如int为0,boolean为false等),但不包括final static变量的直接初始化。
    • 解析(Resolution):将类、接口、字段和方法的符号引用替换为直接引用。
  3. 初始化(Initialization):根据类中的静态代码块和静态字段的初始化代码来初始化类变量。如果类的直接父类还没有被初始化,则先初始化父类。

  4. 使用(Using):类的实例(对象)被创建和使用,JVM通过类的方法表来调用类的方法。

  5. 卸载(Unloading):当类的Class对象不再被JVM引用,并且垃圾回收器(GC)确定该类已经不可能再被使用(即类的所有实例都已被销毁,且没有线程正在引用类的Class对象或类加载器)时,类会被卸载。类的卸载过程由JVM的垃圾回收机制自动完成,开发者通常无法直接控制。

类加载器

        作用:加载Class文件  加载Class字节码文件中的内容到内存中

1. 启动类加载器(Bootstrap ClassLoader)

  • 定义:也称为根加载器或引导类加载器,是JVM内置的类加载器,用于加载Java的核心类库,如rt.jar中的类。
  • 特点:由C++实现,是虚拟机的一部分,因此无法在Java代码中直接获取到它的引用。它负责加载JVM自身需要的类,即$JAVA_HOME/jre/lib下的核心类库。
  • 作用:保证Java平台的独立性,使得Java程序可以在不同的平台上运行而不需要重新编译。

2. 扩展类加载器(Extension ClassLoader)

  • 定义:由Java实现,具体实现类是sun.misc.Launcher$ExtClassLoader
  • 特点:负责加载Java的扩展类库,即$JAVA_HOME/jre/lib/ext目录下的JAR包。这些JAR包通常包含了一些扩展的API,如JDBC驱动程序等。
  • 作用:提供了一种机制,允许开发者在不修改JVM核心类库的情况下,通过添加JAR包来扩展Java的功能。

3. 系统类加载器(System ClassLoader)

  • 定义:也称为应用程序类加载器(Application ClassLoader),是加载用户类路径(Classpath)上类的类加载器。
  • 特点:由sun.misc.Launcher$AppClassLoader实现,是Java中最常用的类加载器之一。它负责加载开发者编写的应用程序以及第三方库中的类。
  • 作用:实现了Java的模块化,使得不同的应用程序可以独立地运行,而不会相互4干扰。

4. 自定义类加载器(Custom ClassLoader)

  • 定义:由用户自行编写并继承了java.lang.ClassLoader类的实现类。
  • 特点:可以根据自定义规则和需求来加载类文件,例如从网络或磁盘上加载类文件、加密和解密类文件等。
  • 作用:提供了极大的灵活性,允许开发者根据实际需求定制类加载的行为,实现一些特殊的功能,如热部署、类隔离等。

类是模板,对象是具体的(实例)

       双亲委派机制

                当一个类加载器需要加载一个类时,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传达到顶层的启动类加载器。只有当父类加载器反馈自己无法完成这个加载请求(即该父类加载器在它的搜索范围中没有找到所需的类)时,子类加载器才会尝试自己去加载。

当一个类需要被加载时,类加载器的双亲委派机制按以下步骤工作:

  1. 检查是否已加载:类加载器首先会检查是否已加载过指定类。如果已加载,则直接返回对应的Class对象。

  2. 向上委派:如果未加载,类加载器会将加载请求委派给其父类加载器。这个过程会一直持续到顶层的启动类加载器。

  3. 父类加载器加载:父类加载器会尝试加载指定的类。如果父类加载器能够加载该类,则直接返回加载的Class对象。

  4. 向下委派:如果父类加载器无法加载该类(即在其搜索范围中没有找到所需的类),则子类加载器会尝试自己加载该类。

  5. 自定义加载器:如果应用程序中定义了自定义加载器,它们也可以参与到这个委派过程中,但通常它们会遵循双亲委派机制的原则。

             LoadClass 会破坏双亲委派机制  FindClass不破坏双亲委派机制

        

  打破双亲委派机制

  1. 自定义类加载器并重写loadClass方法

    自定义类加载器并重写其loadClass方法是打破双亲委派机制最直接的方式。通过loadClass方法的实现,可以自定义类的加载行为,从而绕过双亲委派机制。例如,在Tomcat中,为了实现Web应用之间的类隔离,Tomcat为每个Web应用都创建了一个独立的类加载器,并通过重写loadClass方法来控制类的加载行为。

  2. 使用线程上下文类加载器

    线程上下文类加载器(Thread Context ClassLoader)提供了一种在不改变双亲委派机制的前提下,让线程能够加载不同类加载器所加载的类的方法。在Java中,每个线程都可以设置自己的上下文类加载器,这个类加载器会被用来加载线程中创建的对象或引用的类。例如,在JDBC和JNDI等场景中,就使用了线程上下文类加载器来加载数据库驱动或JNDI资源。

  3. OSGi框架的类加载器

    OSGi(Open Service Gateway initiative)是一个动态模块系统,它实现了一套自己的类加载器机制,允许同级之间委托进行类的加载。OSGi的类加载器机制与双亲委派机制不同,它允许模块之间共享类,同时也支持模块的独立更新和部署。然而,由于OSGi的复杂性和学习曲线,它在企业级应用中的使用相对较少。

程序计数器(私有)

        程序计数器也叫pc寄存器,每个线程会通过程序计数器记录当前要执行的字节码指令的址;

         多线程的情况下cup的切换,程序计数器记录切换前的位置,以至于下次执行知道执行到那个位置

java虚拟机栈(私有)

        Java虚拟机栈是描述Java方法执行过程的内存模型,每个线程在创建时都会创建一个虚拟机栈,其内部保存着一个个栈帧(Stack Frame),每个栈帧对应着一次Java方法调用

        栈内存模型(先进后出)

局部变量表,用于存放方法中的局部变量;

操作数栈:用于存储计算过程中的中间结果,同时作为计算过程中变量临时的存储空间。

动态连接,指向运行时常量池中该栈帧所属方法的引用,用于支持方法的动态链接。

方法返回地址,当方法执行完毕后要返回的位置。

栈帧随着方法的调用而创建,随着方法的结束而销毁,它对方法的执行起到了关键的支撑作用。

   栈内存溢出

        java虚拟机栈如果栈过多,占用内存超过栈内存可以分配的最大大小就会出现溢出   StackOverflowError

        设置大小:-Xss栈大小   字节(默认,必须是1024的倍数)、k(KB)、m(MB)、g(GB)

        注意:HotSpot JVM对栈的大小的最大值和最小值有要求 Windows(64位) JDK8 180k~1024M

                    局部变量过多、操作数栈的深度过大也会影响栈内存的大小

本地方法栈

        java虚拟机栈存储了java方法调用时的栈帧,而本地方法栈存储的是native本地方法的栈帧,

        在HotStop虚拟机中,java虚拟机栈和本地方法栈实现了使用同一栈空间,本地方法栈会在栈内存上生成一个栈帧,临时保存方法的参数同时方便出现异常时也把本地方法栈的信息打印出来。

堆(共享)

        Java堆内存(Java Heap)是Java虚拟机(JVM)所管理的内存中最大的一块,主要用于存放对象实例。

        栈上的局部变量表中,可以存放堆上对象的引用。静态常量也可以存放堆对象的引用,通过静态常量就可以实现对象在线程之间的共享。 

        堆内存的大小是可以调节的。-Xms、-Xmx表示初识堆空间大小和最大堆空间大小。

        大小限制:Xmx必须大于2MB  Xms必须大于1MB

        堆内存溢出:java.lang.OutOfMemoryError: Java heap space

        

方法区(共享)

        方法区与Java堆一样,是各个线程共享的内存区域。它主要用于存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

        Hotspot设计:

        JDK7及以前的版本将方法区放在堆区域中的永久代空间,堆的大小由虚拟机参数来控制。

        堆的大小:-XX:MaxPermSize

        JDK8及以后的版本将方法区存放在元空间中,元空间位于操作系统的直接内存中,默认情况下只要不超过操作系统的承受的上限,可以一直分配。

        元空间大小限制:-XX:MaxMetaspaceSize

        方法区溢出:java.lang.OutOfMemoryError: PermGen space (JDK7)

  

直接内存

        直接内存不在《Java虚拟机规范》中存在,所有不属于Java运行时的内存区域

JDK1.4中引入了NIO机制,使用直接内存,主要解决以下两个问题:

        1.Java堆中的对象如果不再使用要回收,回收时会影响对象的创建和使用。

        2.IO操作比如读文件,想要先把文件读入直接内存(缓冲区)再把数据复制到Java堆中

现在直接放入直接内存即可,同时Java堆上维护直接内存的引用,减少了数据复制的开销,写文件也有类似的思路。

       内存大小调节: -XX:MaxDirectMemorySize   默认JVM自动选择最大分配的大小

自动垃圾回收

方法区回收

  1. 类卸载
    • 当JVM发现某个类不再被使用时,它可能会将其从方法区中卸载。这通常发生在该类的所有实例都被回收,且没有任何方式(如反射)可以访问到该类时。
    • 卸载过程包括标记(遍历所有引用链,标记所有可达的对象)、清理(清理所有不可达的对象,包括类信息、静态变量、常量池等)和卸载(将卸载的类从方法区中移除)。
  2. 内存不足
    • 当方法区中的数据过多,导致内存不足时,JVM可能会回收一些不再使用的数据。这种情况下,JVM会根据一定的策略选择回收哪些数据。

堆回收

        Java中的对象是否能被回收,是根据对象是否被引用来决定的。

 判断堆上的对象有没有被引用

        1.引用计数法:会为每一个对象维护一个引用计数器,当对象被引用时加1,取消引用时减1

             缺点:每次引用和取消引用都想要维护计数器,对系统性能会有一定的影响

                        存在循环引用的问题   Java没有采用

        2.可达性分析算法:当一个对象可以被任何途径访问到时,它是存活的;反之,如果一个对                    象无法被任何途径访问到时,它是垃圾。算法通过一组被称为“GC Roots”的根对象 作                    为起始点,递归地遍历所有对象,标记所有与GC Roots直接或间接相连的对象为存活                     对象,未被标记的对象则被认为是垃圾。   Java采用

GC Roots对象
  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象:包括当前线程中各个栈帧的局部变量表中持有的对象引用。
  2. 方法区(静态变量)中引用的对象:包括类的静态属性所引用的对象,以及常量池中的引用对象(如字符串常量池中的字符串对象)。
  3. 本地方法栈中JNI(即Native方法)引用的对象:包括通过JNI(Java Native Interface)调用的本地代码所引用的对象。
  4. 其他类型的引用:如系统类加载器、JVM内部引用等。
五种对象引用方式
  1. 强引用(Strong Reference)
    • 定义:最常见的引用类型,如Object obj = new Object()。只要强引用还存在,垃圾回收器就永远不会回收掉被引用的对象实例。
    • 特点:强引用是最普通的对象引用关系,如果一个对象具有强引用,那么垃圾回收器绝不会回收它。
  2. 软引用(Soft Reference)
    • 定义:用来描述一些还有用但是不是必须的对象。
    • 特点:在系统将要发生内存溢出异常之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收后还是没有足够的内存,才会抛出内存溢出异常。JDK 1.2之后提供了SoftReference类来实现软引用。
  3. 弱引用(Weak Reference)
    • 定义:也是用来描述非必需对象的,但其强度要弱于软引用。
    • 特点:被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器工作时,无论当前内存是否充足,都会回收掉只被弱引用关联的对象。JDK 1.2之后提供了WeakReference类来实现弱引用。
  4. 虚引用(Phantom Reference)
    • 定义:也被称为幽灵引用或幻影引用,是最弱的一种引用关系。
    • 特点:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。JDK 1.2之后提供了PhantomReference类来实现虚引用。
垃圾回收算法

        

1. 标记-清除算法(Mark-Sweep)
  • 原理:算法分为标记和清除两个阶段。首先,垃圾回收器遍历所有对象,并标记出存活的对象;然后,清除未被标记的对象,即回收其占用的内存。
  • 特点
    • 实现简单,但容易产生内存碎片。
    • 在内存回收后,可能会出现大量的不连续空间,导致后续的大对象分配失败,即使总的可用内存足够。
2. 复制算法(Copying)
  • 原理:将可用内存分为两个大小相等的区域(From,To),每次只使用其中一个区(From)。当这个区域内存满时,将还存活的对象复制到另一个区域,然后清除当前区域的所有对象。
  • 特点
    • 解决了内存碎片问题,因为每次都是对整块内存进行回收和分配。
    • 需要两倍的内存空间,且当对象存活率较高时,复制操作的效率会降低。
3. 标记-压缩算法(Mark-Compact)
  • 原理:标记过程与标记-清除算法相同,但在清除阶段,不是直接清除未标记的对象,而是将所有存活的对象向内存的一端移动,然后清除边界以外的内存。
  • 特点
    • 解决了内存碎片问题,提高了内存的利用率。
    • 需要额外的空间来保存迁移地址,且移动存活对象会增加一定的性能开销。
4. 分代收集算法(Generational Collection)

        在JVM中,堆内存被划分为新生代(Young Generation)和老年代(Old Generation)两个主要区域,有时还包括永久代/元空间(PermGen/Metaspace),但永久代在JDK 8及以后版本中被元空间所取代。

        新生代又被进一步细分为Eden区、From区(也称为Survivor0区或S0区)和To区(也称为Survivor1区或S1区),通常它们的比例是8:1:1

1. Minor GC(Young GC)

  • 当新生代的Eden区空间不足时,会触发Minor GC。
  • Minor GC的目标是快速回收新生代中的垃圾对象。
  • 采用复制算法,将Eden区和From Survivor区中的存活对象复制到To Survivor区,并清空Eden区和From Survivor区。
  • 随后,From Survivor区和To Survivor区的角色互换,为下一次Minor GC做准备。
  • 经过多次Minor GC后,如果对象仍然存活,其年龄会增加,当年龄达到一定阈值(如15岁,具体值可配置)时,对象会被晋升到老年代。

2. Major GC(Old GC)

  • 当老年代的空间不足时,会触发Major GC。
  • Major GC的目标是回收老年代中的垃圾对象。
  • 由于老年代中的对象存活时间较长,因此Major GC的执行时间相对较长。
  • 采用标记-清除算法或标记-整理算法,首先标记出存活的对象,然后清理掉不再存活的对象,并进行内存整理(如果需要)。

3. Full GC

  • 在某些特殊情况下,如老年代空间不足且无法通过Major GC回收足够空间时,会触发Full GC。
  • Full GC会同时清理新生代和老年代中的垃圾对象。
  • Full GC的执行时间较长,可能会导致较长时间的应用停顿。

垃圾回收器(转入)    

7种JVM垃圾回收器详解 & 垃圾收集参数汇总_jvm 收集器parallel scavenge 动态缩小了新生代大小-CSDN博客文章浏览阅读1.3k次。1. jvm垃圾回收器1.1. Serial收集器1.2. ParNew收集器1.3. Parallel Scavenge收集器1.4. Serial Old收集器1.5. Parallel Old收集器1.6. CMS收集器1.7. G1收集器1.8. 总结1.9. 垃圾收集器参数汇总1. jvm垃圾回收器本篇文章主要介绍下图7种垃圾回收器,相关的垃圾回收算法可以看一下之前的文章。只有在新生代和老年代垃圾回收器之间有连线,才可以搭配使用,比如老年代垃圾回收器设置的是C.._jvm 收集器parallel scavenge 动态缩小了新生代大小https://blog.csdn.net/godloveleo9527/article/details/114484180

内存调整大小参数

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值