JVM回顾与Java虚拟机的内存管理

news2024/12/26 11:25:54

目录

什么是JVM? 

主流虚拟机

JVM与操作系统关系

JVM、JRE、JDK的关系

 Java程序的执行过程

JVM翻译字节码有三种执行方式

Java虚拟机的内存管理

JVM整体架构图

JVM运行时内存

 Java7和Java8内存结构的不同主要体现在方法区的实现

对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?

方法区Java8之后的变化

Java8为什么要将永久代替换成Metaspace?

PC程序计数器

PC寄存器的特点

虚拟机栈

什么是虚拟机栈?

 什么是栈帧?

局部变量表

操作数栈

动态链接

方法返回地址

本地方法栈

特点

Java堆

什么是堆?

堆的特点

设置堆空间大小(内存大小-Xmx/-Xms )

堆的分类

 年轻代和老年代

对象分配过程

 堆GC

元空间

永久代与元空间的区别

为什么要废弃永久代,引入元空间?

废除永久代的好处

方法区

方法区的理解

 方法区的特点

方法区结构

方法区设置

运行时常量池

直接内存


什么是JVM? 

        相信大家已经很不陌生了,只要接触编程以及Java的小伙伴们都肯定知道。大概介绍一下。

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

主流虚拟机

虚拟机名称介绍
HotSpotOracle/Sun JDK和OpenJDK都使用HotSPot VM的相同核心
J9J9是IBM开发的高度模块化的JVM
JRockitJRockit与HotSpot同属于Oracle,目前为止Oracle一直在推进HotSpot与JRockit两款各有优势的虚拟机进行融合互补
Zing由Azul Systems根据HostPot为基础改进的高性能低延迟的JVM
DalvikAndroid上的Dalvik虽然名字不叫JVM,但骨子里就是不折不扣的JVM

JVM与操作系统关系

 从图中可以看到,有了JVM这个抽象层之后,Java就可以实现跨平台了。JVM只需要保证能够正确执行.class文件,就可以运行在诸如Linux、Windows、MacOS等平台上。

而Java跨平台的意义在于一次编译,处处运行,能够做到这一点JVM功不可没。比如我们再Maven仓库下载同一个版本的jar包就可以到处运行,不需要再每个平台上再编译一次。

现在的一些JVM的扩展语言,比如Clojure、JRuby、Groovy等,编译到最后都是.class文件,Java语言的维护者,只需要控制好JVM这个解析器,就可以将这些扩展语言无缝的运行再JVM之上了。

JVM与操作系统之间的关系:JVM上承开发语言,下接操作系统,它的中间接口就是字节码。

JVM、JRE、JDK的关系

 JVM是Java程序能够运行的核心。但是需要注意,JVM自己什么也干不了,需要给它提供生产原料(.class文件)。

而且是需要借助于基本的类库,所谓的 JRE(Java Runtime Environment)Java的运行时环境,仅靠JVM是无法完成一次编译的。

 Java程序的执行过程

 这里的Java程序是文本格式的。以下代码它遵循的就是Java语言规范。

其中,我们调用了System.out等模块,也就是JRE里提供的类库。

public class HelloWorld{

    public static void main(){
        System.out.println("Hello World");
    }
}

使用JDK的工具javac进行编译后,会产生HelloWorld的字节码。

Java字节码是沟通JVM与Java程序的桥梁,以下是字节码示例。

0 getstatic #2 <java/lang/System.out> //getstatic 获取静态字段的值

3 ldc #3 <Hello World> //ldc 常量池中的常量值入栈

5 invokevirtual #4 <java/io/PrintStream.println> //invokevirtual 运行时方法绑定调用方法

8 return //void 函数返回

JVM虚拟机采用基于栈的结构,其指令由操作码和操作数组成。这些字节码指令,就叫做opcode。其中,getstatic、ldc、invokevirtual、return等,就是opcode。

JVM就是靠解析这些opcode和操作数来完成程序的执行的。当我们使用java命令运行.class文件的时候,实际上就相当于启动了一个JVM进程。

JVM翻译字节码有三种执行方式

        (1)解释执行,将opcode+操作数翻译成机器码;要开启此模式,使用-Xint参数;

java -Xint -version



java version "1.8.0_71"

Java(TM) SE Runtime Environment (build 1.8.0_71-b15)

Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, interpreted mode)

                缺点: 这种模式会降低运行速度,通常低10倍或者更多。

        (2)JIT(即时编译),它会在一定条件下(不管是否热点代码,对所有的函数)将字节码编译成机器码之后再执行;要开启此模式,使用-Xcomp参数;

java -Xcomp -version



java version "1.8.0_71"

Java(TM) SE Runtime Environment (build 1.8.0_71-b15)

Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, compiled mode)

                缺点:JVM在第一次使用时就会把所有的字节码编译为本地代码, 从而优化执行速度,绕开缓慢的解释器。但是这种模式没有让JVM启用JIT编译器的全部功能。

        (3)混合执行(默认),JVM默认的执行模式,部分函数会解释执行,部分会编译执行。如果函数调用频率高,被反复使用,就会认为是热点代码,该函数就会编译执行。

Java虚拟机的内存管理

JVM整体架构图

 JVM五大模块分为:类装载器子系统、运行时数据区、执行引擎、本地方法接口、垃圾手机模块。

 JVM内存共分为:虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

JVM运行时内存

 Java7和Java8内存结构的不同主要体现在方法区的实现

        方法区是java 虚拟机规范中定义的一种概念上的区域,不同的厂商可以对虚拟机进行不同的实现。
        我们通常使用的Java SE 都是由 Sun JDK OpenJDK 所提供,这也是应用最广泛的版本。而该版本使用的 VM 就是 HotSpot VM。通常情况下,我们所讲的 java 虚拟机指的就是 HotSpot 的版本。
JDK7内存结构

 JDK8的内存结构

 针对JDK8虚拟机内存详解

 JDK7JDK8变化小结

 线程私有的:

        ①程序计数器
        ②虚拟机栈
        ③本地方法栈
线程共享的:
        ①堆
        ②方法区        
        直接内存( 非运行时数据区的一部分 )

对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?

        当然不是,方法区只是一个规范,只不过它的实现变了。

        在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在本地内存(Native memory)。

方法区Java8之后的变化

  • 移除了永久代(PermGen),替换为元空间(Metaspace)。
  • 永久代中的class metadata(类元信息)转移到了natice memory(本地内存,而不是虚拟机)。
  • 永久代中的interned Strings(字符串常量池)和class static variables(类静态变量)转移到了Java heap。
  • 永久代参数(PermSize MaxPermSize)--->元空间参数(MetaspaceSize MaxMetaspaceSize)

Java8为什么要将永久代替换成Metaspace?

  • 字符串存在永久代中,容易出现性能问题和内存溢出。
  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  • 永久代会为GC带来不必要的复杂度,并且回收效率偏低。
  • Oracle可能会将HotSpot与JRockit合二为一,JRockit没有所谓的永久代。

PC程序计数器

程序计数器( Program Counter Register : 也叫 PC 寄存器,是一块较小的内存空间,它可以看做是当前线程所执行 的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条 需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

PC寄存器的特点

  1. 区别于计算机硬件的pc寄存器,两者不略有不同。计算机用pc寄存器来存放“伪指令”或者地址,而相对于虚拟机,pc寄存器它表现为一块内存,虚拟机的pc寄存器的功能也是存放伪指令,更确切的说存放的是将要执行指令的地址。
  2. 当虚拟机正在执行的方法是一个本地(native)方法的时候,jvm的pc寄存器存储的值是undefined。
  3. 程序计数器是线程私有的,它的生命周期与线程相同,每个线程都有一个。
  4. 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处 理器只会执行一条线程中的指令。

因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数
器互不影响,独立存储,我们称这类内存区域为 线程私有 的内存。

虚拟机栈

什么是虚拟机栈?

        Java虚拟机栈 (Java Virtual Machine Stacks) 也是线程私有的 ,即生命周期和线程相同。 Java 虚拟机栈 和线程同时创 建,用于存储栈帧 。每个方法在执行时都会创建一个 栈帧 (Stack Frame) 用于存储 局部变量表 操作数栈 动态 链接 方法出口 等信息。每一个方法从调用直到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
package com.lagou.unit;

public class StackDemo {

    public static void main(String[] args) {
        StackDemo sd = new StackDemo();
        sd.A();
    }

    public void A(){
        int a = 10;
        System.out.println(" method A start");
        System.out.println(a);
        B();
        System.out.println("method A end");
    }

    public void B(){
        int b = 20;
        System.out.println(" method B start");
        C();
        System.out.println("method B end");
    }

    private void C() {
        int c = 30;
        System.out.println(" method C start");
        System.out.println("method C end");
    }
}

 什么是栈帧?

        栈帧(Stack Frame) 是用于支持虚拟机进行方法调用和方法执行的数据结构。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。

 

 设置虚拟机栈的大小

-Xss jvm 启动的每个线程分配的内存大小,默认 JDK1.4 中是 256K JDK1.5+ 中是 1M
  • Linux/x64 (64-bit): 1024 KB
  • macOS (64-bit): 1024 KB
  • Oracle Solaris/x64 (64-bit): 1024 KB
  • Windows: The default value depends on virtual memory
-Xss1m
-Xss1024k
-Xss1048576

局部变量表

        局部变量表(Local Variable Table) 是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。包括 8 种基本数据类型、对象引用(reference类型)和 returnAddress 类型(指向一条字节码指令的地址)。
        其中64 位长度的 long double 类型的数据会占用 2 个局部变量空间( Slot ),其余的数据类型只占用 1 个。

操作数栈

        操作数栈(Operand Stack) 也称作操作栈,是一个后入先出栈 (LIFO) 。随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/ 入栈操作。

动态链接

        Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态链接(Dynamic Linking)

动态链接的作用: 将符号引用转换成直接引用。

方法返回地址

        方法返回地址存放调用该方法的PC寄存器的值。一个方法的结束,有两种方式:正常地执行完成,出现未处理的异常非正常的退出。无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的PC计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

无论方法是否正常完成,都需要返回到方法被调用的位置,程序才能继续进行。

本地方法栈

本地方法栈( Native Method Stacks ) 与虚拟机栈所发挥的作用是非常相似的, 其区别只是虚拟机栈为虚拟机执行 Java方法(也就是字节码) 服务, 而本地方法栈则是为虚拟机使用到的本地(Native )方法服务。

特点


(1)本地方法栈加载native的方法,native类方法存在的意义当然是填补java代码不方便实现的缺陷而提出的。

(2)虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到都得Native方法服务。

(3)是线程私有的,它的生命周期与线程相同,每个线程都有一个。

在Java虚拟机规范中,对本地方法栈这块区域,与Java虚拟机栈一样,规定了两种类型的异常:

(1)StackOverFlowError:线程请求的栈深度>所允许的深度。

(2)OutOfMemoryError:本地方法栈扩展时无法申请到足够的内存。

Java堆

什么是堆?

对于 Java 应用程序来说, Java堆( Java Heap )是虚拟机所管理的内存中最大的一块。 Java 堆是被所 有线程共享的一块内存区域,在虚拟机启动时创建。 此内存区域的唯一目的就是存放对象实例。

堆的特点

序号特点
1shiJava虚拟机所管理的内存中最大的一块。
2

堆是jvm所有线程共享的。

堆中也包含私有的线程缓冲区:Thread Local Allocation Buffer(TLAB)

3在虚拟机启动时创建。
4唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。
5
Java 堆是垃圾收集器管理的主要区域。
6
因此很多时候 java 堆也被称为 “GC Garbage Collected Heap )。从内存回收的角度来看,由于现在收集器
基本都采用分代收集算法,所以 Java 堆还可以细分为:新生代和老年代;新生代又可以分为: Eden 空间、 From
Survivor 空间、 To Survivor 空间。
7
java 堆是计算机物理存储上不连续的、逻辑上是连续的,也是大小可调节的(通过 -Xms -Xmx 控制)。
8
方法结束后 , 堆中对象不会马上移出仅仅在垃圾回收的时候时候才移除。
9
如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError 异常

设置堆空间大小(内存大小-Xmx/-Xms

使用示例 : -Xmx20m -Xms5m
说明: 当下Java 应用最大可用内存为 20M , 最小内存为5M

public class TestVm {
    public static void main(String[] args) {
        //补充
        //byte[] b=new byte[4*1024*1024];
        //System.out.println("分配了4M空间给数组");
        System.out.print("Xmx=");
        System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
        System.out.print("free mem=");
        System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");
        System.out.print("total mem=");
        System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
    }
}
执行结果:
Xmx=18.0M   // 设置的Xmx20m 但实际是18m,这里没太懂
free mem=4.789039611816406M
total mem=9.0M
可以发现,这里打印出来的 Xmx 值和设置的值之间是由差异的,total Memory和最大的内存之间还是存在一定差异的,就是说JVM 一般会尽量保持内存在一个尽可能底的层面,而非贪婪做法按照最大的内存来进行分配。
在测试代码中新增如下语句,申请内存分配:
byte[] b=new byte[4*1024*1024];
System.out.println("分配了4M空间给数组");
在申请分配了 4m 内存空间之后,total memory上升了,同时可用的内存也上升了,可以发现其实 JVM 在分配内存过程中是动态的,按需来分配的。

Xmx=18.0M
free mem=3.751373291015625M
total mem=11.5M

堆的分类

现在垃圾回收器都使用分代理论 , 堆空间也分类如下 :
Java7 Hotspot 虚拟机中将 Java 堆内存分为 3 个部分:
  • 青年代Young Generation
  • 老年代Old Generation
  • 永久代Permanent Generation

 Java8以后,由于方法区的内存不在分配在Java堆上,而是存储于本地内存元空间Metaspace中,所以永久代就不存在了

 年轻代和老年代

1.JVM中存储java对象可以被分为两类:

        1)年轻代(Young Gen)年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分 、成1Eden Space2Suvivor Space(from 和to)。

        2)年老代(Tenured Gen)年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁。

2.配置新生代和老年代堆结构占比:

默认 -XX:NewRatio=2 , 标识新生代占 1 , 老年代占 2 , 新生代占整个堆的 1/3
修改占比 -XX:NewPatio=4 , 标识新生代占 1 , 老年代占 4 , 新生代占整个堆的 1/5
Eden 空间和另外两个 Survivor 空间占比分别为 8:1:1
可以通过操作选项 -XX:SurvivorRatio 调整这个空间比例。 比如 -XX:SurvivorRatio=8。
几乎所有的 java 对象都在 Eden 区创建 , 80% 的对象生命周期都很短 , 创建出来就会被销毁 .

 从图中可以看出:

堆大小 = 新生代 + 老年代 。其中,堆的大小可以通过参数 –Xms -Xmx 来指定。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 90% ) 的新生代空间。

对象分配过程

JVM设计者不仅需要考虑到内存如何分配,在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,因此还需要考虑GC执行完内存回收后是否存在空间中间产生内存碎片。

具体步骤:(仔细看完)

  1. new的对象先放在Eden区。该区域有大小限制。
  2. 当Eden区填满时,程序又需要创建对象, JVM的垃圾回收器将对Eden进行垃圾回收(Minor GC),将Eden区域中不再被其他对象引用的对象进行销毁,再加载新的对象放到Eden
  3. 然后将Eden区中的剩余对象移动到from survivor区
  4. 如果再次触发垃圾回收,此时上次幸存下来的放在from survivor区的,如果没有回收,就会放到 to survivor区
  5. 如果再次经历垃圾回收,此时会重新返回from survivor区,接着再去to survivor区
  6. 如果累计次数到达默认的15次,这会进入Old Memory区
  7. Old Memory区内存不足是会再次触发GC:Major GC进行Old Memory区的内存清理
  8. 如果Old Memory区执行了Major GC后仍然没有办法进行对象的保存,就会报OOM异常

 堆GC

        Java中的堆是GC收集垃圾的主要区域。GC分为两种:一种是部分收集器(Partial GC)另一类是整堆收集器(Full GC)。

部分收集器:(不是完整收集java堆的收集器)

  • 新生代收集器(Minor GC/Yound GC):只是新生代的垃圾收集
  • 老年代收集(Major GC/Old GC):只是老年代的垃圾收集(CMS GC单独回收老年代)
  • 混合收集(Mixed GC):收集整个新生代及老年代的垃圾收集(G1 GC会混合回收,region区域回收)

整堆收集(Full GC):收集整个java堆喝方法区的垃圾收集器

年轻代GC触发条件:

  • 年轻代空间不足,就会触发MinorGC,这里年轻代指的是Eden代满,Survivor不满不会引发GC
  • Minor GC会引发STW(stop the world),暂停其他用户的线程,等垃圾回收接收,用户的线程才恢复.

老年代GC(Major GC)触发机制:

  • 老年代空间不足时,会尝试触发MinorGC,如果空间还是不足,则触发Major GC
  • 如果Major GC,内存仍然不足,则报错OOM
  • Major GC的速度比Minor GC慢10倍以上
    • 慢的原因有两个:
      • 1) major gc使用的算法复杂度比minor gc高
      • 2) major gc使用的算法复杂度比minor gc高

Full GC触发机制:

  • 调用System.gc(),系统会执行Full GC,不是立即执行
  • 老年代空间不足
  • 方法区空间不足
  • 通过Minor GC进入老年代平均大小 大于老年代可用内存

元空间

        在JDK1.7之前,HotSpot虚拟机把方法区当成永久代来进行垃圾回收。从JDK1.8开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中,HotSpots取消了永久代,方法区是一个规范,规范没变,只不过取代永久代的是元空间(Metaspace)。

永久代与元空间的区别

区别永久代元空间
存储位置不同永久代在物理上是堆的一部分,和新生代、老年代的地址是连续的元空间属于本地内存
存储内容不同永久代用来存放类的元数据信息、静态变量以及常量池等

类的元信息存储在元空间;

静态变量和常量池等并入堆中;

相当于原来的永久代中的数据,被原空间和堆内存给瓜分了。

为什么要废弃永久代,引入元空间?

  • 在原来的永久代划分中,永久代需要存放类的元数据、静态变量和常量等。它的大小不容易确定,因为这其中又很多影响因素,比如类的总数,常量池的大小和方法数量等,-XX:MaxPermSize指定太小很容易造成永久代内存溢出。
  • 移除永久代是为融合HotSpot VM与JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
  • 永久代会为GC带来不必要的复杂度,并且回收效率偏低。

废除永久代的好处

  • 由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。不会遇到永久代存在时的内存溢出错误。
  • 将运行时常量池从PermGen(永久代)分离出来,与类的元数据分开,提升类元数据的独立性。
  • 将元数据从PermGen(永久代)剥离出来到Metaspace(元空间),可以提升对元数据的管理同时提升GC效率。

Metaspace相关参数

  • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整;如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  • -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。如果没有使用该参数来设置类的元数据的大小,其最大可利用空间是整个系统内存的可用空间。JVM也可以增加本地内存空间来满足类元数据信息的存储。
    但是如果没有设置最大值,则可能存在bug导致Metaspace的空间在不停的扩展,会导致机器的内存不足;进而可能出现swap(交换分区)内存被耗尽;最终导致进程直接被系统kill掉。
  • -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少分配空间所导致的垃圾收集
  • -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

方法区

方法区的理解

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

《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但是一些简单的实现可能不会选择去进行垃圾收集或者进行压缩”。对HotSpot而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。

元空间、永久代是方法区具体的落地实现。方法区看作是一块独立于Java堆的内存空间,它主要是用来存储所加载的类信息的

创建对象各数据区域的声明:

 方法区的特点

  • 方法区与堆一样是各个线程共享的内存区域
  • 方法区在JVM启动的时候就会被创建并且它实例的物理内存空间和Java堆一样都可以不连续
  • 方法区的大小跟堆空间一样 可以选择固定大小或者动态变化
  • 方法区的对象决定了系统可以保存多少个类,如果系统定义了太多的类导致方法区溢出虚拟机同样会抛出(OOM)异常(Java7之前是PermGen Space(永久代),Java8之后是MetaSpace(元空间))
  • 关闭JVM就会释放这个区域的内存

方法区结构

方法区的内部结构

 类加载器将Class文件加载到内存之后,将类的信息存储到方法区中

方法区中存储的内容:

  • 类型信息(域信息、方法信息)
  • 运行时常量池

 类型信息

对每个加载的类型(类Class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:

        ① 这个类型的完整有效名称(全名 = 包名.类名)

        ② 这个类型直接父类的完整有效名(对于 interface或是java.lang. Object,都没有父类)

        ③ 这个类型的修饰符(public, abstract,final的某个子集)

        ④ 这个类型直接接口的一个有序列表

域信息

域信息,即为类的属性,成员变量

JVM必须在方法区中保存类所有的成员变量相关信息及声明顺序。

域的相关信息包括:域名称、域类型、域修饰符(public、private、protected、static、final、volatile、transient的某个子集)

方法信息

JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:

  1. 方法名称方法的返回类型(或void)
  2. 方法参数的数量和类型(按顺序)
  3. 方法的修饰符public、private、protected、static、final、synchronized、native、abstract的一个子集
  4. 方法的字节码bytecodes、操作数栈、局部变量表及大小(abstract和native方法除外)
  5. 异常表(abstract和native方法除外)。每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

方法区设置

        方法区的大小不必是固定的,JVM可以根据应用的需要动态调整

JDK7及以前:

  • 通过-xx:Permsize来设置永久代初始分配空间。默认值是20.75M
  • -XX:MaxPermsize来设定永久代最大可分配空间。32位机器默认是64M64位机器模式是82M
  • JVM 加载的类信息容量超过了这个值,会报异常 OutofMemoryError:PermGen space
查看JDK PermSpace区域默认大小
  • jps #java提供的一个显示当前所有java进程pid的命令
  • jinfo -flag PermSize 进程号 #查看进程的PermSize初始化空间大小
  • jinfo -flag MaxPermSize 进程号 #查看PermSize最大空间

JDK8以后:

元数据区大小可以使用参数 -XX:MetaspaceSize和 -XX:MaxMetaspaceSize指定。

默认值依赖于平台。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,即没有限制。

与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。如果元数据区发生溢出,虚拟机一样会抛出异常OutOfMemoryError:Metaspace。

-XX:MetaspaceSize:设置初始的元空间大小。对于一个64位的服务器JVM来说,其默认的            -xx:MetaspaceSize值为21MB。这就是初始的高水位线,一旦触及这个水位线,FullGC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活)然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值。

如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到FullGC多次调用。为了避免频繁地GC,建议将-XX:MetaspaceSize设置一个相对较高的值。

jps   #查看进程号

jinfo -flag MetaspaceSize   进程号          #查看Metaspace 最大分配内存空间

jinfo -flag MaxMetaspaceSize  进程号    #查看Metaspace 最大空间

运行时常量池

常量池和运行时常量池区别

存放位置不同概念不同
常量池字节码文件中存放编译期间生成的各种字面量和符号引用
运行时常量池方法区中常量池表在运行时的表现形式

        编译后的字节码文件中包含了类型信息、域信息、方法信息等。通过ClassLoader将字节码文件的常量池中的信息加载到内存中,存储在了方法区的运行时常量池中。

        理解为字节码中的常量池Constant pool只是文件信息,它想要执行就必须加载到内存中。而Java程序是靠JVM,更具体的来说是JVM的执行引擎来解释执行的。执行引擎在运行时常量池中取数据,被加载的字节码常量池中的信息时放到了方法区的运行时常量池中。

        它们不是一个概念,存放的位置是不同的。一个在字节码文件中,一个在方法区中。

以下是对字节码文件反编译之后,查看常量池相关信息:

一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含一项信息那就是 常量 池表 (Constant pool table),包括各种字面量和对类型、域和方法的符号引用。
常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
常量池表 Constant pool table

 

 

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分。

在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能, 因为避免了在Java堆和Navtice堆中来回复制数据。

 NIO的Buffer提供一个可以直接访问系统物理内存的类——DirectBuffer。DirectBuffer类继承ByteBuffer,但和普通的ByteBuffer不同。普通的ByteBuffer仍在JVM堆上分配内存,其最大内存受到最大堆内存的限制。而DirectBuffer直接分配在物理内存中,并不占用堆空间。在访问普通的ByteBuffer时,系统总是会使用一个“内核缓冲区”进行操作。而DirectBuffer所处的位置,就相当于这个“内核缓冲区”。因此,使用DirectBuffer是一种更加接近内存底层的方法,所以它的速度比普通的ByteBuffer更快。

 通过使用堆外内存,可以带来以下好处:

  1. 改善堆过大时垃圾回收效率,减少停顿。Full GC时会扫描堆内存,回收效率和堆大小成正比。Native的内存,由OS负责管理和回收。
  2. 减少内存在Native堆和JVM堆拷贝过程,避免拷贝损耗,降低内存使用。
  3. 可突破JVM内存大小限制。

作者:筱白爱学习!!

欢迎关注转发评论点赞沟通,您的支持是筱白的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366769.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hadoop MapReduce基本概念与详细流程

Hadoop MapReduce是Hadoop 中一个批量计算的框架&#xff0c;在整个mapreduce作业的过程中&#xff0c;包括从数据的输入&#xff0c;数据的处理&#xff0c;数据的数据输入这些部分&#xff0c;而其中数据的处理部分就要map&#xff0c;reduce&#xff0c;combiner等操作组成。…

刚接手的APP项目需要优化,需要从哪些方向入手?

对于每个Android 开发团队来说产品上线&#xff0c;是让人喜忧参半的一件事。**喜指的是&#xff1a;付出了大量的时间&#xff0c;产品终于上线了&#xff1b;而忧指的是&#xff1a;担心中间会不会出现一些性能相关的问题&#xff0c;比如卡顿、内存泄漏、崩溃……等&#xf…

干翻 nio ,王炸 io_uring 来了 !!(图解+史上最全)

大趋势&#xff1a;全链路异步化&#xff0c;性能提升10倍 随着业务的发展&#xff0c;微服务应用的流量越来越大&#xff0c;使用到的资源也越来越多。 在微服务架构下&#xff0c;大量的应用都是 SpringCloud 分布式架构&#xff0c;这种架构总体上是全链路同步模式。 全链…

java 抽象类 详解

目录 一、抽象类概述&#xff1a; 二、抽象方法 : 1.概述 : 2.应用 : 3.特点 : 三、抽象类特点 : 1.关于abstract关键字 : 2.抽象类不能被实例化&#xff0c;只能创建其子类对象 : 3.抽象类子类的两个选择 &#xff1a; 四、抽象类的成员 : 1.成员变量 : 2.成员方…

趣味三角——第12章——tanx

第12章节 tanx In his very numerous memoires, and especially in his great work, Introductio in analysin infinitorum (1748), Euler displayed the most wonderful skill in obtaining a rich harvest of results of great interest. . . . Hardly any other work …

业务单据堆积如山?如何提升会计做账效率?

某集团以“创建现代能源体系、提高人民生活品质”为使命&#xff0c;形成了贯通下游分销、中游贸易储运、上游生产的清洁能源产业链和涵盖健康、文化、旅游、置业的生命健康产品链。目前&#xff0c;某集团在全国21个省&#xff0c;为超过2681万个家庭用户、21万家企业提供能源…

Android:同步屏障的简单理解和使用

同步屏障的简单理解和使用1、背景2、何为同步屏障&#xff1f;2.1、 发送屏障消息——postSyncBarrier2.2、发送异步消息2.3、处理消息2.4、移除屏障消息——removeSyncBarrier2、系统什么时候添加同步屏障&#xff1f;参考1、背景 这里我们假设一个场景&#xff1a;我们向主线…

网狐服务端C++引入http功能剖析

旗舰版本的网狐服务端及以前的版本都是没有http解析功能的&#xff0c;导致就是web后台改了配置不能及时通知到游戏里面去&#xff0c;以至于很多小公司拿这种框架来开发的变通的方案就是用定时器不定时去刷数据库&#xff0c;导致多少个功能就有多少个定时去刷新&#xff0c;代…

0基础学习软件测试难么

0基础开始学习任何一样事情都是有一定难度的&#xff0c;但是也要有对比&#xff0c;软件测试相比于IT行业其他学科已经算是容易入门的了&#xff0c;就看你个人的学习方法&#xff0c;找的学习资源以及你的自制力。 正确学习方法路径 “我一听就懂&#xff0c;一敲就废&…

【Spring的事务传播行为有哪些呢?Spring事务的隔离级别?讲下嵌套事务?】

如果你想寻求一份与后端相关的开发工作&#xff0c;那么关于Spring事务相关的面试题你就不能说不会并且不能不知道&#xff1f; 人生如棋&#xff0c;我愿为卒&#xff0c;行动虽慢&#xff0c;可谁曾见我后退一步&#xff1f; 一.Spring中声明事务的方式 1.1 编程式事务 编程…

Android Gradle脚本打包

1、背景资料 1.1 Android-Gradle-Groovy-Java-JVM 之间的关系 1.2 Android Gradle Plugin Version版本 与 Gradle Version版本的对应关系 Android Gradle Plugin Version版本Gradle Version版本1.0.0 - 1.1.32.2.1 - 2.31.2.0 - 1.3.12.2.1 - 2.91.5.02.2.1 - 2.132.0.0 -…

WFP网络过滤驱动-限制网站访问

文章目录前言WFP入门介绍WFP基本架构名词解释代码基本结构代码示例前言 WFP Architecture - Win32 apps | Microsoft Learn是一个网络流量处理平台。WFP 由一组连接到网络堆栈的钩子和一个协调网络堆栈交互的过滤引擎组成。 本文使用WFP&#xff0c;实现了一个网络阻断的demo…

Guna UI WinForms 2.0.4.4 Crack

Guna.UI2 WinForms is the suite for creating groundbreaking desktop app UI. It is for developers targeting the .NET Windows Forms platform. 50 多个 UI 控件 具有广泛功能的综合组件可帮助您开发任何东西。 无尽的定制 只需拖放即可创建视觉效果命令和体验。 出色的…

服务端开发之Java备战秋招面试篇2-HashMap底层原理篇

现在Java应届生和实习生就业基本上必问HashMap的底层原理和扩容机制等&#xff0c;可以说是十分常见的面试题了&#xff0c;今天我们来好好整理一下这些知识&#xff0c;为后面的秋招做足准备&#xff0c;加油吧&#xff0c;少年。 目录 1、HashMap集合介绍 2、HashMap的存储…

S2-001漏洞分析

首发于个人博客&#xff1a;https://bthoughts.top/posts/S2-001漏洞分析/ 一、简介 1.1 Struts2 Struts2是流行和成熟的基于MVC设计模式的Web应用程序框架。 Struts2不只是Struts1下一个版本&#xff0c;它是一个完全重写的Struts架构。 1.2 S2-001 Remote code exploit o…

【保姆级】手把手捋动态代理流程(JDK+Cglib超详细源码分析)

简介动态代理&#xff0c;通俗点说就是&#xff1a;无需声明式的创建java代理类&#xff0c;而是在运行过程中生成"虚拟"的代理类&#xff0c;被ClassLoader加载。 从而避免了静态代理那样需要声明大量的代理类。上面的简介中提到了两个关键的名词&#xff1a;“静态…

C语言进阶(九)—— 函数指针和回调函数、预处理、动态库和静态库的使用、递归函数

1. 函数指针1.1 函数类型通过什么来区分两个不同的函数&#xff1f;一个函数在编译时被分配一个入口地址&#xff0c;这个地址就称为函数的指针&#xff0c;函数名代表函数的入口地址。函数三要素&#xff1a; 名称、参数、返回值。C语言中的函数有自己特定的类型。c语言中通过…

多元回归分析 | ELM极限学习机多输入单输出预测(Matlab完整程序)

多元回归分析 | ELM极限学习机多输入单输出预测(Matlab完整程序) 目录 多元回归分析 | ELM极限学习机多输入单输出预测(Matlab完整程序)预测结果评价指标基本介绍程序设计参考资料预测结果 评价指标 -----------ELM结果分析-------------- 均方根误差(RMSE):0.55438 测试集…

webgis高德地图

webgis高德地图 首先准备工作,注册一个高德地图账号,然后在创建一个新应用生一个key跟appId 高德开放平台 接着创建一个html页面 高德配置手册 <style>* {margin: 0;padding: 0;}#

如何维护固态继电器?

固态继电器是SSR的简称&#xff0c;是由微电子电路、分立电子器件和电力电子功率器件组成的非接触式开关。隔离装置用于实现控制端子与负载终端之间的隔离。固态继电器的输入端使用微小的控制信号直接驱动大电流负载。那么&#xff0c;如何保养固态继电器呢&#xff1f; 在为小…