运行时的数据区域
程序计数器
程序计数器(Program Counter Register
)是一块较小的内存空间,是当前线程所执行的字节码的行号指示器,作用是用来选取下一条需要执行的字节码指令 各条线程之间计数器互不影响,独立存储,线程私有的内存区域 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空 此区域不会有内存溢出(OutOfMemoryError
)
虚拟机栈
每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程 垃圾回收不涉及栈内存
线程安全
多个线程执行以下方法时,每个线程都会创建一个栈帧存储变量x,即该变量属于线程私有,不会产生线程安全问题
static void m1 ( ) {
int x = 0 ;
for ( int i = 0 ; i < 5000 ; i++ ) {
x++ ;
}
System. out. println ( x) ;
}
如果方法内局部变量没有逃离方法的作用访问,它是线程安全的 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全public static void m1 ( ) {
StringBuilder sb = new StringBuilder ( ) ;
sb. append ( 1 ) ;
sb. append ( 2 ) ;
sb. append ( 3 ) ;
System. out. println ( sb. toString ( ) ) ;
}
public static void m2 ( StringBuilder sb) { 有入口,m2有可能被多个线程调用,不安全
sb. append ( 1 ) ;
sb. append ( 2 ) ;
sb. append ( 3 ) ;
System. out. println ( sb. toString ( ) ) ;
}
public static StringBuilder m3 ( ) {
StringBuilder sb = new StringBuilder ( ) ;
sb. append ( 1 ) ;
sb. append ( 2 ) ;
sb. append ( 3 ) ;
return sb;
}
内存溢出
本地方法栈
Java虚拟机可以通过本地方法接口调用本地方法
堆
通过new创建对象会在堆分配内存 堆中对象都是线程共享的,需要考虑线程安全问题 有垃圾回收机制
内存溢出
public class Demo1_5 {
public static void main ( String[ ] args) {
int i = 0 ;
try {
List< String> list = new ArrayList < > ( ) ;
String a = "hello" ;
while ( true ) {
list. add ( a) ;
a = a + a;
i++ ;
}
} catch ( Throwable e) {
e. printStackTrace ( ) ;
System. out. println ( i) ;
}
}
}
-- -- -- -- -- -- -- -- -- 报错
java. lang. OutOfMemoryError: Java heap space
at java. util. Arrays. copyOf ( Arrays. java: 3332 )
at java. lang. AbstractStringBuilder. ensureCapacityInternal ( AbstractStringBuilder. java: 124 )
at java. lang. AbstractStringBuilder. append ( AbstractStringBuilder. java: 448 )
at java. lang. StringBuilder. append ( StringBuilder. java: 136 )
at cn. itcast. jvm. t1. heap. Demo1_5. main ( Demo1_5. java: 19 )
24
方法区
方法区(Method Area
)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
内存溢出
public class Demo1_8 extends ClassLoader {
public static void main ( String[ ] args) {
int j = 0 ;
try {
Demo1_8 test = new Demo1_8 ( ) ;
for ( int i = 0 ; i < 10000 ; i++ , j++ ) {
ClassWriter cw = new ClassWriter ( 0 ) ;
cw. visit ( Opcodes. V1_8, Opcodes. ACC_PUBLIC, "Class" + i, null, "java/lang/Object" , null) ;
byte [ ] code = cw. toByteArray ( ) ;
test. defineClass ( "Class" + i, code, 0 , code. length) ;
}
} finally {
System. out. println ( j) ;
}
}
}
-- -- -- -- -- -- -- -- -- -- - 报错:元空间导致的溢出
5411
Exception in thread "main" java. lang. OutOfMemoryError: Metaspace
at java. lang. ClassLoader. defineClass1 ( Native Method)
at java. lang. ClassLoader. defineClass ( ClassLoader. java: 763 )
at java. lang. ClassLoader. defineClass ( ClassLoader. java: 642 )
at cn. itcast. jvm. t1. metaspace. Demo1_8. main ( Demo1_8. java: 23 )
运行时常量池
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址 对最简单的HelloWorld.java
编译成的class文件反编译,查看类的基本信息public class HelloWorld {
public static void main ( String[ ] args) {
System. out. println ( "hello world" ) ;
}
}
反编译命令:javap -v HelloWorld.class
------------类基本信息
Classfile /D:/downloads/资料 解密JVM/代码/jvm/out/production/jvm/cn/itcast/jvm/t5/HelloWorld.class
Last modified 2020-11-2; size 567 bytes
MD5 checksum 8efebdac91aa496515fa1c161184e354
Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorld
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 // hello world
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // cn/itcast/jvm/t5/HelloWorld
#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 Lcn/itcast/jvm/t5/HelloWorld;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 HelloWorld.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 hello world
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 cn/itcast/jvm/t5/HelloWorld
#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 cn.itcast.jvm.t5.HelloWorld();//无参构造方法
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 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcn/itcast/jvm/t5/HelloWorld;
public static void main(java.lang.String[]);//main方法
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 hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"
字符串常量池
常量池中的字符串仅是符号,第一次用到时才变为对象,懒加载
public class Demo1_22 {
public static void main ( String[ ] args) {
String s1 = "a" ;
String s2 = "b" ;
String s3 = "ab" ;
String s4 = s1 + s2;
String s5 = "a" + "b" ;
System. out. println ( s3 == s5) ;
}
}
字符串常量池底层使用HashTable
,来避免重复创建字符串 字符串变量拼接的原理是 StringBuilder
(1.8)public class Demo1_21 {
public static void main ( String[ ] args) {
String s1 = "a" ;
String s2 = "b" ;
String s3 = "a" + "b" ;
String s4 = s1 + s2;
String s5 = "ab" ;
String s6 = s4. intern ( ) ;
System. out. println ( s3 == s4) ;
System. out. println ( s3 == s5) ;
System. out. println ( s3 == s6) ;
}
}
直接内存
常见于 NIO 操作时,用于数据缓冲区 分配回收成本较高,但读写性能高 不受 JVM 内存回收管理
分配回收原理
使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
public class Demo1_27 {
static int _1Gb = 1024 * 1024 * 1024 ;
public static void main ( String[ ] args) throws IOException {
Unsafe unsafe = getUnsafe ( ) ;
long base = unsafe. allocateMemory ( _1Gb) ;
unsafe. setMemory ( base, _1Gb, ( byte ) 0 ) ;
System. in. read ( ) ;
unsafe. freeMemory ( base) ;
System. in. read ( ) ;
}
public static Unsafe getUnsafe ( ) {
try {
Field f = Unsafe. class . getDeclaredField ( "theUnsafe" ) ;
f. setAccessible ( true ) ;
Unsafe unsafe = ( Unsafe) f. get ( null) ;
return unsafe;
} catch ( NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException ( e) ;
}
}
}
ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存