[JVM]——JVM内存模型

目录

程序计数器:

本地方法栈:

虚拟机栈:

灵魂四问❓

方法区: 

区分串池和常量池

intern()方法应用实例 

StringTable:

堆:

直接内存(操作系统中的):


程序计数器:

程序计数器会保存下一条指令的地址!!!

如果是64位操作系统,也就是有64根地址线,那么地址对应的大小就是8个字节

本地方法栈:

存储一些用C++语言编写的 native方法

虚拟机栈:

函数入栈形成栈帧,函数执行完出栈,销毁栈帧。

每个线程都有自己的方法栈。

栈中的数据不会被垃圾回收

灵魂四问❓

栈帧包括什么:

        局部变量表 - 保存this和变量的。注意局部变量是可以复用内存槽的,int占1个槽位 long占2个槽位

栈内存会被JVM回收嘛:

        不会,方法调用完出栈

栈内存分配的越大越好吗:

        不是,因为栈内存大了,线程数就少了,并发少了,只是你单线程能调用的方法多了一点,并不让你运行速度变快,反而会慢。 

方法内的局部变量是线程安全的吗?

        是的,因为每一个线程的栈帧是私有的,不会共享那个变量,如果变量加了static那就要考虑线程安全问题了。还有就是变量在参数或者返回值也都面临线程安全问题。

方法区: 

方法区(Method Area)是Java虚拟机(JVM)内存中的一个逻辑区域,它主要存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。具体来说,方法区可以存放以下信息:

  1. 类型信息:这是关于类的元数据,比如类的全名、父类的全名、实现的接口、访问修饰符(public、protected、private等)、字段信息(字段名、字段类型、字段修饰符等)、方法信息(方法名、返回类型、参数类型、方法修饰符等)、常量池、异常表等。

  2. 常量池:常量池是方法区的一部分,它主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量包括文本字符串、声明为final的常量值等。符号引用则包括类和方法的全限定名、字段的名称和描述符以及方法的名称和描述符。

  3. 字段信息:包括类的实例字段(静态字段除外)的信息,比如字段名、字段类型、字段修饰符等。这些信息用于创建类的实例时分配内存和初始化字段。

  4. 方法信息:包括方法的字节码(Bytecodes)、操作数栈的大小、局部变量表的大小、异常表、方法的访问修饰符等信息。字节码是Java方法经过编译器编译后生成的一种中间代码,用于在JVM上执行。

  5. 即时编译器编译后的代码:当JVM的即时编译器(JIT Compiler)将热点代码(频繁执行的方法)编译成机器码后,这些机器码也会存储在方法区中,以便后续直接执行,提高执行效率。

  6. 静态变量:类的静态变量(包括静态字段和静态常量)也会被存储在方法区中。这些变量在类加载时就被分配了内存,并且它们的生命周期与类相同。

注意:HotSpot JVM中静态变量和常量池在堆中,方便JVM管理。


区分常量池和运行时常量池和串池
  1. 常量池(Constant Pool)
    • 位置:常量池通常位于Java类文件中,当类被加载到JVM时,常量池中的信息会被加载到方法区中的运行时常量池中。
    • 作用:存储编译时期生成的各种字面量和符号引用,如字符串、整数、类名、方法名等。
  2. 运行时常量池(Runtime Constant Pool)
    • 位置:运行时常量池位于JVM的方法区中。当类被加载到JVM时,会创建该类对应的运行时常量池,并将常量池中的信息复制到运行时常量池中。
    • 作用:存储运行时所需要的常量,这些常量可以是编译器生成的,也可以是运行时动态生成的。运行时常量池是动态变化的,可以在运行时添加或删除常量。
  3. 串池(String Pool)
    • 位置:从JDK7开始,字符串常量池(String Pool)被移动到堆内存中。在之前的JDK版本中,它可能位于方法区。
    • 作用:字符串常量池用于存储字符串对象。当创建字符串对象时,JVM会首先检查字符串常量池中是否已经存在相同的字符串,如果存在则直接返回该字符串的引用,否则在堆中创建新的字符串对象并将其引用存入字符串常量池。

关系

  • 常量池与运行时常量池:常量池是类文件中定义的,当类被加载时,其常量池中的信息会被加载到运行时常量池中。因此,常量池和运行时常量池是编译时和运行时两个不同的阶段,但存储的信息是相关的。
  • 运行时常量池与串池:运行时常量池中的字符串常量在需要时会被加载到串池中。串池实际上是在运行时对字符串对象进行管理的一个区域,用于实现字符串的共享和节省内存空间。
intern()方法应用实例 

场景还原:流传推特要存储用户名和用户地址,但是这需要30个G的内存空间,并且呢,这些用户许多地址都是重复的,那么使用了intern之后,内存直接从30G变成了几百MB。

intern原理

        将字符串放到Stringtable中,Stringtable是内存中维护的一个Hashtable,也就是那个运行时常量池,它是由数组+链表/红黑树构成的,不允许重复,存储时根据hashcode算出的地址值,存到相应的位置。

        intern方法:

                如果StringTable中存在该字符串,直接返回字该字符串的引用,如果不存在,将该字符串的引用放入到StringTable中。

String str1 = new String("a") + new String("b");
String str2 = str1.intern();
System.out.println(str2 == "ab"); // true
StringTable:

        StringTable内,如果对象未被使用,当Table满时,也会触发垃圾回收机制。

        通过-XX:StringTableSize=【n】来调节桶的大小,桶越大,速度越快

堆:

new 出来的对象就是在堆中存储的

堆中的对象会被JVM垃圾回收器回收。

直接内存(操作系统中的):

        如果Java要使用操作系统中的内存,需要创建两处缓存,一处是系统缓存区,一处是Java缓存区,这样就影响性能,如果使用bytebuffer中的某些方法创建的缓存,可以直接被我们Java程序使用。有待补充~

猜你喜欢

转载自blog.csdn.net/Panci_/article/details/136665808