jvm官方文档
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html
运行时数据区
数据区的描述
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5
The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits. Other data areas are per thread. Per-thread data areas are created when a thread is created and destroyed when the thread exits.
Java虚拟机定义了在程序执行期间使用的各种运行时数据区域。其中一些数据区域是在Java虚拟机启动时创建的,只有在Java虚拟机退出时才会销毁。其他数据区域是每个线程。每个线程的数据区域在线程创建时创建,在线程退出时销毁。
总结:
1.jvm创建的时候,退出的时候销毁
2.每个Thread独有,Thread创建时常见,Thread退出时销毁。
==>有一些区是共享的,有一些区是线程自己的 独享的
运行时数据区
jvm运行时的数据区有一下6个
-
The
pc
Register - Java Virtual Machine Stacks
- Heap
- Method Area
- Run-Time Constant Pool
- Native Method Stacks
还有一个非jvm的: 堆外内存
如果调用底层源码的时候,可以使用堆外内存。但是不调用底层源码的时候,尽量不要使用堆外内存。
1.The pc
Register 程序计数器
JVM 能够支持多线程执行。每一个JVM线程都有自己独有的pc register。任何时间节点,每个JVM都都在每一个执行执行的代码上。
占用一小块的内存,计数器为 当前线程锁执行的字节码的行号指示器。
记录当前线程目前执行到代码所对应的的哪一条字节码指令。
比如在多线程方式运行程序时,会多个线程并发处理,每个线程都有自己的程序计数器来标记自己的线程执行到哪里。
假如A线程在运行,B运行拿到的执行权非常高,这个时候A会被停止下来。等到B运行完成后,A会抢占资源继续执行,这个时候就需要A线程执行时的字节码。
那么什么是字节码呢?
通过不同的字节码的指令,来让应用程序清楚程序应该做什么事情,aload_0 就是字节码指令
(1)写一段最简单的java代码:HelloWord.java
public class HelloWord { public static void main(String[] args) { System.out.println("Hello Word!"); } }
(2)HelloWord.java只是我们自己开发完成的java源代码,这个时候需要使用将代码编译成字节码class文件。
javac HelloWord.java
(3) 使用javap将class文件写入到text文本中
javap -verbose HelloWord.class >> HelloWord.txt
HelloWord.txt的内容如下
Classfile /Users/wangyichao/Desktop/HelloWord.class
Last modified 2019-10-26; size 423 bytes
MD5 checksum 7c22fac0e6a2b992b4e072a2e28ef0df
Compiled from "HelloWord.java"
public class HelloWord
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // Hello Word!
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // HelloWord
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 HelloWord.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 Hello Word!
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 HelloWord
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
{
public HelloWord();
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 1: 0public 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 Hello Word!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 4: 0
line 5: 8
}
SourceFile: "HelloWord.java"
2.Java Virtual Machine Stacks JVM栈
每个Java虚拟机线程都有一个私有的Java虚拟机堆栈,与线程同时创建。JVM里会存储frames(栈帧) 什么是frames在后面会进行讲解。
如果计算的线程需要的大小比JVM栈大,就会抛出StackOverflowError的错误(比如方法掉方法本身死循环)
调动方法时,会为每个方法创建frame 入栈,当方法执行完毕之后,会执行出栈。
非native或非java本身的的会放在jvm栈中,和 Native Method Stacks 对比看一下。
3.Heap
JVM有一个堆,堆是JVM线程共享的一个区域。堆是为所有类实例和数组分配内存的运行时数据区域。
堆会在虚拟机创建的开启的时候被创建。堆会存储一些对象,当然这个对象会被一个 自动的存储管理系统( garbage collector) 释放。
如果一个计算需要非常多个Heap,比整个我们能用的存储系统还要大的时候,这个时候会抛出OutOfMemoryError(比如不停的实例化对象或者一个非常大的数组)
延伸
但是存在一个问题就是,当JVM栈中的一些方法使用完成出栈之后,堆中分配的内存就是无实在意义了。这个时候就要GC来讲堆中的数据进行回收。这个就涉及到内存回收的东西。
4.Method Area 在1.8以后叫Metaspace
jvm里有一个方法区,这个方法区也是JVM线程共享的。
方法区在vm创建的时候创建。
方法区存储pool, field and method data, and the code for methods and constructors
说白了就是class文件加载到方法区。
如果方法区域中的内存不能满足分配请求OutOfMemoryError
5.Run-Time Constant Pool
每个类或每个接口的运行时都会有常量池,这个常量池其实就是存放在Method Area。
在创建类或接口时,如果构建运行时常量池所需的内存超过了Java虚拟机的方法区域所能提供的内存,就会抛出 OutOfMemoryError的异常
6.Native Method Stacks
当JVM使用JAVA以外的编写的方法(例如C堆栈)或者是native修饰的方法时,会放在native的方法栈中。
如果线程中的计算需要比允许的更大的本机方法堆栈,会抛出StackOverflowError异常
如果可以动态扩展本机方法堆栈,并且尝试扩展本机方法堆栈,但是可用的内存不足,或者如果可用的内存不足,无法为新线程创建初始本机方法堆栈,会抛出OutOfMemoryError异常
native修饰的,就会存放在native的方法栈中
非native修饰的方法 是调用本地操作系统里面的方法
JVM图解
Frames
A frame is used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.
Frame通常是用来存储数据和部分结果。
局部变量等就会存放在fram里。
当一个方法被调用的时候,一个Frame就会被创建。当方法被执行完成的时候就会销毁掉frame。
例如下面这个代码:
当实例化 hello这个方法的时候,frame就会被创建,当执行完 hello.hello后,会销毁掉frame
public static void main(String[] args) { HelloWord hello = new HelloWord(); hello.hello(); } public void hello() { String name = "java"; //局部变量,存放在 Java Virtual Machine Stacks System.out.println("Hello:" + name); }
案例分析
1.分析程序中的输出结果
String s1 = new String("TestString"); //产生了两个个对象:1.String()存在heap里 2.方法区里面存储常量 s1,c常量值为ruozedata String s2 = "TestString"; System.out.println(s1 == s2);//结果为false,比较的是引用的值,他们的地址是不一样的 System.out.println(s1.equals(s2));//结果为true,equals真实的值,和hashcode有关
s1 s1是一个变量,是对象的引用,它产生了两个对象 1.String()存在heap里 2.方法区里面存储常量 s1,c常量值为ruozedata
是常量,因为Method Area已经有了值,s2不会产生对象
2.分析下面程序的输出结果
String s1 = "我爱"; String s2 = "大数据"; String s3 = "我爱" + "大数据"; String s4 = s1 + s2; String s5 = "我爱大数据"; String s6 = s4.intern(); System.out.println(s3 == s4);//false System.out.println(s3 == s5);//true System.out.println(s4 == s5);//false System.out.println(s3 == s6);//true
结果分析
3.分析结果
Integer i1 = 50; Integer i2 = 50; Integer i3 = 150; Integer i4 = 150; Double d1 = 10.1D; Double d2 = 10.1D; System.out.println(i1 == i2);//true System.out.println(i3 == i4);//falseSystem.out.println(d1 == d2);//false
为什么i1 == i2 是true,i3 == i4 是false
基本类型:int long
包装类型:Integer String
包装类型会有一个实例化的过程
我们看Interger.java的源码和Double.java的源码
Integer.java
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
在源码中我们可以看到 [-128,127]在java的pool中,在这个范围内的值,都是true。超出这个范围,会新创建一个对象,实例化的东西都会在Heap中,所以超出这个范围内的值 用 ==肯定是false的。
Double.java
public static Double valueOf(String s) throws NumberFormatException { return new Double(parseDouble(s)); }