Java内存区域
1.前提概要
在虚拟机自动内存管理机制下,Java不像C、C++程序开发那样需要手动开辟释放内存空间。不容易出现内存溢出(Out Of Memory)、内存泄漏(Memory Leak)。但并不代表就不会出现,了解了Java的内存区域布局,就能更清晰的去定位问题和解决问题
2.运行时数据区域
不同的数据区域的生命周期各不相同,有的随着JVM Process启动而一直存在,有的则是随着用户线程的启动和结束而建立和销毁。
拓展:
用户线程和守护线程
用户线程:Thread.setDaemon(false)
守护线程:Thread.setDaemon(true)
线程创建出来默认为用户线程
用户线程和守护线程的区别:
1.只要有用户线程在运行,JVM就会一直存活
2.如果没有用户线程,或者都是守护线程,那么JVM结束(所有的线程都会结束)
3.守护线程依赖操作系统
1.程序计数器
3.本地方法栈
4.Java堆
5.方法区
6.运行时常量池
7.直接内存
一图概览(图片为转载,如有侵权,请联系)
1.程序计数器
它是当前线程执行的字节码行号指示器。它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等操作都需要依赖这个计数器来完成。它是线程私有的。此内存区域不存在OOM情况。
①线程执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址
②线程执行本地(native)方法,则记录的为空(Undefined)
2.Java虚拟机栈
它是线程私有的,和线程的生命周期相同。它描述的是Java方法执行的线程内存模式。每个方法被执行的时候,都会创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法从开始调用到执行结束的过程,就对应着一个栈帧从入栈到出栈的过程。
①局部变量表
用于存放编译期可知的JVM基本数据类型(8大基本数据类型)、对象引用(它并不是对象本身,可能是指向对象的地址引用指针,也可能是指向一个代表对象的句柄或者与对象位置相关联的信息)和returnAddress(指向了一条字节码指令的地址)
局部变量表所需的内存空间是在编译器间完成分配的
②操作数栈
用于计算的临时数据存储区域,和局部变量表进行交互。它用来执行JVM指令,用来给局部变量表中的变量进行数据操作的,比如赋值、求和(iconst_0:将int类型常量压入操作数栈、istore_1:将int类型常量压入栈、imul、iadd、bipush等操作,这些JVM指令翻译成机器码由CPU去执行)。
③动态连接
在运行的过程中,把符号引用转换成方法对应的代码的直接引用
编译期会把一些静态方法、变量加载出来,此为静态连接。实例方法不像静态方法是在程序加载的过程中解析的,而是在运行到该方法的时候进行解析的,运行态期间如果调用了实例方法,会把方法区存储的该实例方法的符号引用转变成直接引用
④方法出口
方法返回指令 : 执行引擎遇到一个方法返回的字节码指令,这时候有可能会有返回值传递给上层的方法调用者,这种退出方式称为正常完成出口。
异常退出 : 在方法执行过程中遇到了异常,并且没有处理这个异常,就会导致方法退出。
3.本地方法栈
它和虚拟机栈类似,区别在于:虚拟机栈执行Java方法,本地方法栈执行本地(native)方法
4.Java堆
它是JVM管理的最大的一块内存。它是线程共享的内存区域。它是用来存放对象实例。它是被垃圾收集器管理的内存区域。按照回收内存的角度来看,分为Eden、From Survior、To Survior、Old Gen,这些分区只是站在垃圾收集器的角度去划分的区域,并不是JVM划分的内存区域。
Java Heap 可以处于物理上不连续的内存空间,但是在逻辑上应当被视为连续的。对于一些大对象,例如数组,它就是连续的内存空间。它的大小是可以通过JVM参数来控制的(-Xmx、-Xms)
5.方法区
它是线程共享的内存区域,用来存放JVM加载的类元信息、常量、静态变量、即使编译器编译后的代码缓存等数据。
JDK8以前方法区的实现是永久代,永久代并不等价于方法区。我的理解是方法区是一个接口,而永久代就是它的一种实现。
JDK7的时候就把原本存放在永久代的字符串常量池、静态变量等移到Java Heap中。
JDK8的时候用在直接内存实现的元空间(MetaSpace)来替代永久代,并且把一些其他的数据(类元信息)移到元空间中。
6.运行时常量池
它是方法区的一部分,但是我浏览了一些网上的博客,发现有的也说JDK7以后运行时常量池和字符串常量池在Java Heap中。此处存疑。
Class文件除了类元信息(字段、方法、接口等描述信息),还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的字面量与符号引用,这些内容会在类加载后被放到运行时常量池中(下列反编译的代码中Constant pool的列表信息)。
Last modified 2021-1-19; size 1026 bytes
MD5 checksum ddd20c8bf14696f053b6798bbb50cc65
Compiled from "Demo.java"
public class com.redis.Demo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#39 // java/lang/Object."<init>":()V
#2 = Fieldref #40.#41 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Class #42 // java/lang/StringBuilder
#4 = Methodref #3.#39 // java/lang/StringBuilder."<init>":()V
#5 = String #43 // c =
#6 = Methodref #3.#44 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#7 = Methodref #3.#45 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#8 = Methodref #3.#46 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Methodref #47.#48 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = Class #49 // com/redis/Demo
#11 = Methodref #10.#39 // com/redis/Demo."<init>":()V
#12 = Methodref #10.#50 // com/redis/Demo.test:()V
#13 = Class #51 // java/lang/Object
#14 = Utf8 STR
#15 = Utf8 Ljava/lang/String;
#16 = Utf8 ConstantValue
#17 = String #52 // str
#18 = Utf8 name
#19 = Utf8 <init>
#20 = Utf8 ()V
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 LocalVariableTable
#24 = Utf8 this
#25 = Utf8 Lcom/redis/Demo;
#26 = Utf8 test
#27 = Utf8 a
#28 = Utf8 I
#29 = Utf8 b
#30 = Utf8 c
#31 = Utf8 main
#32 = Utf8 ([Ljava/lang/String;)V
#33 = Utf8 args
#34 = Utf8 [Ljava/lang/String;
#35 = Utf8 demo
#36 = Utf8 MethodParameters
#37 = Utf8 SourceFile
#38 = Utf8 Demo.java
#39 = NameAndType #19:#20 // "<init>":()V
#40 = Class #53 // java/lang/System
#41 = NameAndType #54:#55 // out:Ljava/io/PrintStream;
#42 = Utf8 java/lang/StringBuilder
#43 = Utf8 c =
#44 = NameAndType #56:#57 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#45 = NameAndType #56:#58 // append:(I)Ljava/lang/StringBuilder;
#46 = NameAndType #59:#60 // toString:()Ljava/lang/String;
#47 = Class #61 // java/io/PrintStream
#48 = NameAndType #62:#63 // println:(Ljava/lang/String;)V
#49 = Utf8 com/redis/Demo
#50 = NameAndType #26:#20 // test:()V
#51 = Utf8 java/lang/Object
#52 = Utf8 str
#53 = Utf8 java/lang/System
#54 = Utf8 out
#55 = Utf8 Ljava/io/PrintStream;
#56 = Utf8 append
#57 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#58 = Utf8 (I)Ljava/lang/StringBuilder;
#59 = Utf8 toString
#60 = Utf8 ()Ljava/lang/String;
#61 = Utf8 java/io/PrintStream
#62 = Utf8 println
#63 = Utf8 (Ljava/lang/String;)V
{
public static final java.lang.String STR;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String str
public com.redis.Demo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/redis/Demo;
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: ldc #5 // String c =
20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: iload_3
24: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
27: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
30: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: return
LineNumberTable:
line 15: 0
line 16: 2
line 17: 4
line 18: 8
line 19: 33
LocalVariableTable:
Start Length Slot Name Signature
0 34 0 this Lcom/redis/Demo;
2 32 1 a I
4 30 2 b I
8 26 3 c I
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #10 // class com/redis/Demo
3: dup
4: invokespecial #11 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #12 // Method test:()V
12: return
LineNumberTable:
line 22: 0
line 23: 8
line 24: 12
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 args [Ljava/lang/String;
8 5 1 demo Lcom/redis/Demo;
MethodParameters:
Name Flags
args
}
SourceFile: "Demo.java"
运行时常量池相比Class类文件常量池具有动态特性,运行期间也可以将新的常量放入池中,比如String类的intern()方法。
运行时常量池也会OOM。
7.直接内存
可以理解成机器的物理内存,它不是JVM运行时数据区域的一部分,它不会受到Java Heap的大小限制,但它受限于本机物理内存的限制。也会发生OOM。