内存结构
程序计数器
1.定义
在Java虚拟机(JVM)中,每个线程都有一个独立的程序计数器,它是线程私有的,不会被线程切换所影响。
2.作用
记住下一条jvm指令的执行地址
3.特点
- 是线程私有的
- 不会存在内存溢出
虚拟机栈
1.定义
虚拟机栈(Virtual Machine Stack)是Java虚拟机(JVM)内存结构中的一部分,用于存储方法执行的信息,每个线程都有自己独立的虚拟机栈。每个方法在执行时都会在虚拟机栈上创建一个栈帧
2.特点
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
3.栈内存溢出
- 栈帧过多导致栈内存溢出
- 栈帧过大导致栈内存溢出
举例:无限递归调用。
本地方法栈
1.定义
本地方法栈是java虚拟机内存结构中的一部分,与虚拟机栈类似,但是是专门用于执行本地方法(native method)的过程。每个线程都有自己独立的本地方法栈。
2.作用
本地方法栈的作用是为java程序调用本地方法提供支持。本地方法使用c或者c++语言编写的。
3.与虚拟机栈的不同
虚拟机栈用于执行java方法的,本地方法栈用于执行本地方法。
堆
1.定义
用于存储java程序运行时创建的对象实例和数组。所有对象的实例都存储在堆中。
2.特点
动态分配:堆内存是动态分配的,它的大小可以在程序运行时动态的调整
对象存储:堆内存主要用于存储Java程序运行时创建的对象实例和数组,包括通过new关键字创建的对象和数组
垃圾回收:堆内存中的对象不再被引用的时候,会由java虚拟机的垃圾回收器自动进行垃圾回收。
线程共享:它是线程共享的,堆中对象都需要考虑线程安全的问题
3.堆内存溢出(java.long.OutOfMemoryError:java heap space)
当程序中创建了大量的对象实例,而这些对象实例无法被垃圾回收器及时回收时,堆内存的空间会被耗尽,导致堆内存溢出。
4.堆内存诊断
jps工具 :查看当前系统中有哪些java进程
jmap工具:查看堆内存占用情况 jmap - heap进程id
jconsole工具:图形界面的,多功能的检测工具,可以连续检测
方法区
1.定义
Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区。方法区类似于传统语言的编译代码的存储区或者类似于操作系统进程中的"文本"段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的 特殊方法
2.特点
- 方法区是在虚拟机启动时创建的
- 方法区在逻辑上是堆的一部分,但简单的实现可以选择不进行垃圾收集或压缩它
3.组成
在JDK 8之前,方法区被称为永久代,它具有固定大小,并且使用的是堆内存。但是,由于永久代的大小是有限的,且垃圾回收器不会主动回收永久代中的内存,所以在大量类的加载和卸载时,容易出现永久代溢出的情况。
在JDK 8之后,永久代被元空间(Metaspace)所取代。元空间仍然用于存储类的结构信息、常量、静态变量等,但是它不再位于堆内存中,而是位于本地内存中。元空间的大小受限于本地内存的大小,因此不再存在永久代溢出的问题。
通过编译反编译可以获得二进制字节码;二进制字节码通常由(类基本信息,常量池,类方法定义,包含了虚拟机指令)
常量池
常量池就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息。常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量 池,并把里面的符号地址变为真实地址
运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分,它是在类加载过程中生成的,用于存储编译时期生成的各种字面量和符号引用。与Class文件中的常量池(Class Constant Pool)不同,运行时常量池是在类加载后动态生成的,用于满足某些需要在运行期间才能确定的常量。
StringTable
StringTable是 Java 虚拟机(JVM)中用于存储字符串的一种数据结构,它也叫字符串池(String Pool)
字符串池是运行时数据区域的一部分,用于存储Java中的字符串对象实例。字符串池中存储的是字符串对象的引用,而不是字符串对象本身。当创建字符串对象时,如果字符串池中已经存在相同内容的字符串,则返回字符串池中的引用,否则创建一个新的字符串对象,并将其放入字符串池中。
字符串池和常量池的区别
-
字符串池中的字符串对象通常是常量池中字符串常量的引用。当创建字符串对象时,如果该字符串在常量池中已经存在,则返回常量池中的引用,否则将新的字符串常量添加到常量池中,并返回该引用。
-
由于字符串是不可变的,因此字符串池中的字符串对象可以被共享。这意味着如果多个字符串对象的内容相同,则它们都会指向字符串池中的同一个字符串对象实例,从而节省了内存空间。
StringTable特性
常量池中的字符串仅仅是符号,第一次用到时才变为对象
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是StringBuilder
字符串常量拼接的原理是编译器优化
可以使用intern方法,主动将串池中还没有的字符串对象放入池中,也就是字符串变量拼接而成的字符串。如果有的话,并不会放入,如果没有放入串池,会把串池中的对象返回
示例
java
public class Test5 {
public static void main(String[] args) {
String s = "cd";
String s1 = new String("a")+new String("b");
String s2 = s1.intern();
String s3 = new String("c")+new String("d");
String s4 = s3.intern();
System.out.println(s1==s2);//true
System.out.println(s3==s4);//false
}
}
StringTable垃圾回收
在java中,StringTable不会被垃圾回收,因为StringTable是存储字符串对象引用的数据结构,它是JVM内部的一部分,并且是线程私有的,不会被垃圾回收器所管理。
需要注意的是,虽然StringTable
本身不会被垃圾回收,但其中的字符串对象可能会被垃圾回收器回收,具体取决于是否存在对这些字符串对象的引用。如果没有任何引用指向字符串对象,那么这些对象将会被垃圾回收器回收,释放内存空间。
StringTable调优
StringTable底层采用哈希表结构,也就是数组+链表,数组的个数也称为桶个数。
方法一:
-XX:StringTableSize=<size>
:指定StringTable桶
的大小,即哈希表的初始容量。默认值通常是与堆空间大小相关联的一个数值。
方法二:
考虑将字符串对象是否入池。
直接内存
定义
直接内存是JVM中一种特殊的内存分配方式,它与Java堆内存和栈内存不同,直接内存不是由java虚拟机管理的,而是由操作系统管理的一块内存区域。它通常用于提高I/O操作的性能和效率。
特点
- 非堆内存分配:直接内存不属于Java堆内存的一部分,也不受Java虚拟机的垃圾回收机制管理。
- 零拷贝:直接内存通常与NIO(New I/O)相关,可以通过直接内存实现零拷贝技术,提高I/O操作的性能和效率。
- 使用ByteBuffer类 :在Java中,可以使用
ByteBuffer
类来操作直接内存,通过allocateDirect()
方法来分配直接内存的缓冲区。
分配回收原理
1.使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法 2.ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调 用 freeMemory 来释放直接内存