浅谈jvm

         了解jvm,我是从这几个方面去了解的:概念,组成,内存划分,jvm内存模型,类加载,gc

 

         1.概念: JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

 

        2.组成:

    JVM 的结构基本上由 4 部分组成:

    类加载器,在 JVM 启动时或者类运行时将需要的 class 加载到 JVM 中

    执行引擎,执行引擎的任务是负责执行 class 文件中包含的字节码指令,相当于实际机器上的 CPU

    内存区,将内存划分成若干个区以模拟实际机器上的存储、记录和调度功能模块,如实际机器上的各种功能的寄存器或者 PC 指针的记录器等

    本地方法调用,调用 C 或 C++ 实现的本地方法的代码返回结果

 

      3.内存划分:

  方法区(线程共享):各个线程共享的一个区域,用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。运行时常量池:是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。

  堆内存(线程共享):所有线程共享的一块区域, 垃圾收集器管理的主要区域。目前主要的垃圾回收算法都是分代收集算法,所以 Java 堆中还可以细 分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等, 默认情况下新生代按照8:1:1的比例来分配。根据java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘一样。

  程序计数器: Java 线程私有,类似于操作系统里的 PC 计数器,它可以看做是当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器值则为空(Undefined)。此内存区域是唯一 一个在 Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。

  虚拟机栈(栈内存):Java线程私有,虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的时候,都会创建一个栈帧用于存储局部变量、操作数、动态链接、方法出口等信息;每个方法调用都意味着一个栈帧在虚拟机栈中入栈到出栈的过程;

  本地方法栈 :和Java虚拟机栈的作用类似,区别是该区域为 JVM 提供使用 native 方法的服务

 

       4.jvm内存模型和jmm模型

JMM的主要目标是定义程序中变量的访问规则

 

 

             

          

 

 

      5.类加载:

             简述机制: 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型。

             类加载过程

                     加载

          加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:
          1. 通过一个类的全限定名获取该类的二进制流。
          2. 将该二进制流中的静态存储结构转化为方法去运行时数据结构。 
          3. 在内存中生成该类的Class对象,作为该类的数据访问入口。
       验证
          验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:
          1. 文件格式验证:验证字节流是否符合Class文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.
          2. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
          3. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正                                                确,跳转指令是否正确等。
          4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。
       准备
          准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随                                      着对象一起分配在Java堆中。
                                    public static int value=123;//在准备阶段value初始值为0 。在初始化阶段才会变为123
       解析
           该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。
         初始化
          初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

 

    什么是类加载器,类加载器有哪些?

      实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
      主要有一下四种类加载器:
        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类的方式实现。
 
 

    双亲委派机制描述?

    某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
 
 

    为何要采取双亲委派机制

    委托机制的意义 — 防止内存中出现多份同样的字节码
    比如两个类A和类B都要加载System类:
    如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。
 
 
    什么是内存溢出和内存泄露
       1.内存泄漏(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。通常少次数的内存无法及时回收并不会到程序造成什么影响,但是如果在内存本身就比较少获取多次导致内存无法正常回收时,就会导致内存不够用,最终导致内存溢出。
       2.内存溢出 (out of memory)::指程序申请内存时,没有足够的内存供申请者使用,导致数据无法正常存储到内存中。也就是说给你个int类型的存储数据大小的空间,但是却存储 一个long类型的数据,这样就会导致内存溢出。

 

 

     6.gc:

  简述java垃圾回收机制?

    在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空                 闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
 
 
       垃圾回收器有哪些?
  1. Serial(串行GC)-复制
  2. ParNew(并行GC)-复制
  3. Parallel Scavenge(并行回收GC)-复制
  4. Serial Old(MSC)(串行GC)-标记-整理
  5. CMS(并发GC)-标记-清除
  6. Parallel Old(并行GC)--标记-整理
  7. G1(JDK1.7update14才可以正式商用)
  说明:
  1. 1~3用于年轻代垃圾回收:年轻代的垃圾回收称为minor GC
  2. 4~6用于年老代垃圾回收(当然也可以用于方法区的回收):年老代的垃圾回收称为full GC
  3. G1独立完成"分代垃圾回收"
  注意:并行与并发
  1. 并行:多条垃圾回收线程同时操作
  2. 并发:垃圾回收线程与用户线程一起操作
 
  几种垃圾回收机制:
      
  (1)标记-清除(Mark-Sweep)算法
这是最基础的算法,就像它名字一样,算法分为“标记”和“清除”两个阶段:首先标记处所有需要回收的对象(如哪些内存需要回收所描述的对象),对标记完成后统一回收所有被标记的对象,如下图所示:
缺点:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除后悔产生大量的不连续的内存碎片,可能会导致后续无法分配大对象而导致再一次触发垃圾收集动作。
  (2)复制算法
为了针对标记-清除算法的不足,复制算法将可用内存容量划分为大小相等的两块,每次只使用一块。当一块的内存用完了,就将还存活的对象复制到另一块上面去。然后把已使用过的内存空间一次清理掉,如下图所示:
缺点:使用内存比原来缩小了一半。
现在的商业虚拟机都采用这种收集算法来回收新生代,有企业分析的得出其实并不需求将内存按1:1的比例划分,因为新生代中的对象大部分都是“朝生夕死”的。所以,HotSpot虚拟机默认的Eden和Survivor的大小比例是8:1。一块Eden和两块Survivor,每次使用一块Eden和一块Survivor,也就是说只有10%是浪费的。如果另一块Survivor都无法存放上次垃圾回收的对象时,那这些对象将通过“担保机制”进入老年代了。
  (3)标记-整理(Mark-Compact)算法
复制算法一般是对对象存活率较低的一种回收操作,但对于对象存活率较高的内存区域(老年代)来说,效果就不是那么理想了,标记-整理算法因此诞生了。标记-整理算法和标记-清除算法差不多,都是一开始对回收对象进行标记,但后续不是直接对对象清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,如下图所示:
  (4)分代收集算法
  分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
 
 
 
 
 
 
 
 
 
 
 
 
 

      

posted @   雪域飞魂  阅读(80)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示