• 灵魂拷问JVM,你被干趴了吗


    大家好,我是小轩,Java内存模型在面试过程中必不可少,最近也是整理了一些笔记。

    Java的类加载过程

    jvm将.class类文件信息加载到内存并解析成对应的class对象的过程,注意:jvm并不是一开始就把所有的类加载进内存中,只是在第一次遇到某个需要运行的类才会加载,并且只加载一次

    主要分为三部分:

    1、加载,2、链接(1.验证,2.准备,3.解析),3、初始化

    一、加载

    1、类加载器

    上面已经提到了类加载的过程是由类加载来完成,Java内部提供了几种类加载器:

    启动类加载器(Bootstrap Class Loader)

    扩展类加载器(Extension Class Loader)

    应用程序类加载器(Application Class Loader)

    ① 启动类加载器(Bootstrap Class Loader)

    负责加载存放在JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等。

    ② 扩展类加载器(Extension Class Loader)

    主要负责加载存在在JRE的lib/ext目录下的JAR包以及通过java.ext.dirs系统变量指定的路径中的JAR包。

    ③ 应用程序类加载器(Application Class Loader)

    主要负责加载ClassPath路径下的所有JAR包。

    开发人员还可以通过自定义类加载器来进行扩展。

    2、双亲委派机制

    Java中类加载器之间的关系通过双亲委派机制来保证

    0b1d3537f303b61186cc2448891b4443.png

    双亲委派机制确保在加载每个类时,由当前的类加载器委派给它的父(上层)类加载器加载,依次类推直到启动类加载器,如果启动类加载器在加载的路径下找不到目标类,则再交给子(下层)类加载器加载,同样依次类推,如果所有的父加载器都找不到目标类,则在当前类加载器的路径中查找并加载目标类。

    简单来说就是父加载器优先加载,父加载器无法加载再由子加载器加载。

    好处就是可以避免重复加载。

    二、链接

    验证:(验证class文件的字节流是否符合jvm规范)

    准备:为类变量分配内存,并且进行赋初值

    解析:将常量池里面的符号引用(变量名)替换成直接引用(内存地址)过程,在解析阶段,jvm会把所有的类名、方法名、字段名、这些符号引用替换成具体的内存地址或者偏移量。

    三、初始化

    主要对类变量进行初始化,执行类构造器的过程,换句话说,只对static修试的变量或者语句进行初始化。

    范例:Person person = new Person();为例进行说明。

    Java编程思想中的类的初始化过程主要有以下几点:

    1. 找到class文件,将它加载到内存

    2. 在堆内存中分配内存地址

    3. 初始化

    4. 将堆内存地址指给栈内存中的p变量

    上面的过程发生在Java虚拟机中

    f4f18b83573d84d300dca3e1d163c814.png

    Java虚拟机一句话说就是:Java 虚拟机就是我们java应用的运行环境,Java语言使用Java虚拟机屏蔽操作系统和机器之间的差异,达到Write Once Run Anywhere。

    通常由javac 去编译程序源代码,转换成Java字节码,JVM通过解析字节码文件,将其翻译成对应的机器指令,逐条读入,逐条解析。

    JVM整体结构

    f73708f400371a6f735f56cc73f111c6.png

    JVM称为Java虚拟机,它要对应用程序的线程等分配内存空间,还要负责加载类、解析执行编译后的字节码文件,所以整个JVM主要就包含了运行时数据区、类加载子系统和字节码执行引擎。

    运行时数据区是在内存中的,所以这一部分也成为JVM的内存模型。这里要与JMM(Java内存模型)区分开。

    内存模型主要包含五部分的内容:堆、栈、本地方法栈、方法区(元空间)、程序计数器。

    堆:JVM管理的最大一块内存空间,它是所有线程所共享的一块区域。在虚拟机启动的时候创建,该区域的唯一目的就是为了存放对象实例。

    栈(虚拟机栈):也可以称为虚拟机线程栈,它是JVM中每个线程所私有的一块空间,每个线程都会有这么一块空间。虚拟机栈描述了Java中方法执行时的内存模型,即每个方法被执行的时候,线程都会在自己的线程栈中同步创建一个栈帧(Stack Frame),用于存放局部变量表、操作数栈、动态连接和方法出口等信息,每个方法从调用到完成的过程,就对应着一个栈帧在线程栈中从入栈到出栈的过程。

    本地方法栈:本地方法栈与虚拟机栈的作用是相似,不同的是虚拟机栈为JVM执行的Java方法服务,而本地方法栈为JVM调用的本地方法服务。

    方法区:在JDK 8之前,方法区也称之为永久代,这部分区域与堆一样,是所有线程所共享的,它主要用于存放被虚拟机加载的类型信息、常量、静态变量以及即时编译器编译后的代码缓存等数据。

    程序计数器:每个线程都会有自己独立的程序计数器,主要功能就是记录当前线程执行到哪一行指令了,只需要一小块内存空间。

    JVM内存参数

    261786fa8759a5cefa066e4b8c5c81d2.png

    JVM内存模型中的这些区域,都是有大小限制的,当然也可以通过JVM提供的参数来设置这些区域所占内存的大小。

    下面介绍这些常用参数的含义:

    -Xss:表示每个线程栈的大小

    -Xms:表示堆空间的初始可用大小,默认为物理内存的1/64

    -Xmx:表示堆空间的最大可以大小,默认为物理内存的1/4(这个参数在应用程序中是一定要指定的)

    -Xmn:表示新生代(年轻代)的大小

    -XX:NewRatio:默认为2,表示新生代占年老代的1/2,占整个堆内存的1/3。

    -XX:SurvivorRatio:默认为8,表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。

    -XX:MaxMetaspaceSize:设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。

    -XX:MetaspaceSize:指定元空间触发Full Gc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整:如果释放了大量的空间, 就适当降低该值;如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。

    由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般推荐将这两个值都设置为256M。

  • 相关阅读:
    AtCoder abc 136
    【多目标跟踪】 TrackFormer 耗时三天 单句翻译!!!
    设计模式之代理模式
    使用Inis搭配内网穿透实现Ubuntu上快速搭建博客网站远程访问
    根据关键词取商品列表 API 返回值说明
    关于时间片调度算法issue的分析与解决
    代码随想录 Day40 动态规划08 LeetCodeT198打家劫舍 T213打家劫舍II T337 打家劫舍III
    Leetcode.670 最大交换 中等
    【Linux】CentOS 虚拟机
    java编译时的sourcepath选项
  • 原文地址:https://blog.csdn.net/weixin_38754799/article/details/126756739