一、JVM初识
JVM组成部分 20210201155824403.jpg.png数据类型分为两类:
- 基本数据类型(8个):byte,short,int,long,float,double,boolean,char
- 引用数据类型:除了基本数据类型其它全是引用数据类型,比如类、数组
数据类型在内存中存储:
- 基本数据类型只有一块存储空间(分配在栈stack中),传递的时候直接是值传递,对数据进行操作,不影响原先的值
- 引用类型有两块存储空间(一块在栈stack中,一块在堆heap中)
在堆中会开辟一块内存空间存储真实的数据,在栈中会存放一个引用,该引用存放堆内存的地址。
二、JVM内存结构
2.1 程序计数器
该内存是给线程
使用的:每个线程有独立的计数器,其作用
是记录下一条jvm的指令地址;是内存区中唯一不存在内存溢出问题的。
2.2 虚拟机栈
该内存是给线程
使用的,其作用
是让线程独立运行。栈是一个动态的存储区域。
每个线程运行的方法由:局部变量
、参数和返回值类型组成。这些组成部分在被以栈帧的形式封装并压入栈中;每个线程运行只有一个活动栈帧,即只有一个方法正在运行。
设置每个线程栈的大小为128K
-Xss128k <==> -XX:ThreadStackSize=128k
栈的内存溢出情况:
- 方法的递归调用,没有正确结束,导致栈中栈帧过多。
- 单个栈帧过大,直接超过栈内存,此种情况很少出现。
2.3 本地方法栈
调用由C/C++实现的方法。
2.4 堆
new 的对象都放在堆中,属于线程共享区域,需要考虑线程安全问题。
栈属于线程方法执行的区域,所以栈就像客栈一样,属于临时区域,对象占用的内存空间随着出栈而释放;而堆相对于栈,一旦被new创建出来,对象的销毁需要垃圾回收机制的介入。
设置jvm堆空间的大小,默认值为4G;堆内存溢出的情况:对象被回收前,超过了堆的内存空间。建议:开发测试阶段,将堆内存空间设置的相对小些,尽早的发现java.lang.OutOfMemoryError: Java heap space
。
堆内存的诊断工具:
jps: 查看当前系统有哪些java进程。
jmap:查看堆内存使用情况。 jmap - heap 进程id
jconsole:图形化的内存检测工具。
jvisualjvm:可视化的虚拟机工具。
2.5 方法区
虚拟机规范建议:方法区逻辑上应是堆的组成部分,在虚拟机启动时候创建。方法区的规范实现方式不同:永久代 jdk6 和元空间 jdk8。
方法区:1.6~1.8线程共享区域,存储类结构的相关信息:成员变量、成员方法,构造器方法等。
方法区的内存溢出:因为jdk8使用的系统内存,所以不易出现元空间内存溢出(java.lang.OutOfMemoryError:Metaspace
)的情况。
运行时常量池:
首先要知道,类的二进制字节码文件包含:类的基本信息、方法的定义、虚拟机指令,最后就是常量池。
常量池就是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型和字面量。
运行时常量池:当类被加载,常量池中的数据就会放入内存中的运行时常量池,并把里面的符号地址变为真实地址。
源码:
public class Main {
public static void main(String[] args) {
System.out.println("信息");
}
}
Java -v Main.class 反编译的字节码文件:
Classfile /Users/xuezengbo/ABO/myself/myCode/javese/out/production/javese/com/company/Main.class
Last modified 2022-8-18; size 534 bytes
MD5 checksum a915cf12884a408279dca589bd8ac469
Compiled from "Main.java"
public class com.company.Main
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // 信息
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // com/company/Main
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/company/Main;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 Main.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 信息
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 com/company/Main
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
{
public com.company.Main();
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 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/company/Main;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String 信息
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
StringTable:是运行时常量池的重要组成部分,是存放运行时字符串包括类名、方法名等。
s3是存放在StringTable内存中的;而s4是存放在堆内存中;s4在编译期间不能优化 javac编译期间优化后:s3等同于s5,所以s3==s5在1.8 中intern将某个字符串放入StringTable中时,如果StringTable中没有对应的字符串,则会把该字符串放入StringTable,并把StringTable中的对象返回。
在1.6 中intern将某个字符串放入StringTable中时,如果StringTable中没有对应的字符串,则会把该字符串复制一份放入StringTable,并把StringTable中的对象返回。
1.8 使用intern可以将StringTable没有的字符串放入StringTable:放入成功 1.8 使用intern可以将StringTable没有的字符串放入StringTable:放入失败
网友评论