Java虚拟机标准运行时数据区相干地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5
JVM虚拟机标准翻译
运行时数据区模型
在Java虚拟机中把内存分为若干个不同的数据区域。这些区域有各自的用处,有些区域随着虚拟机过程启动而存在,有些区域则依赖用户线程的启动和完结而建设和销毁。在JVM中次要分为以下几个区域:
- 程序计数器
- 办法区
- 虚拟机栈
- 本地办法栈
- Java堆
程序计数器
作用
程序计数器内存占用较小,是以后线程执行的字节码的行号指示器。字节码解释器就是通过这个扭转这个计数器的值来选取下一条须要执行的字节码指令,它是程序控制流的指示器,分支、循环、调整、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。
为什么是线程公有的?
Java虚拟机的多线程是通过线程轮流切换、调配处理器执行工夫的形式来实现的,在任何一个确定时刻,一个处理器(对于多核CPU来说是一个内核)只会执行一条线程的指令。因而为了线程切换后能复原到正确的执行地位,每条线程都须要一个公有的程序计数器,各条线程之间的计数器互不影响,独立存储。
为什么须要计数器?
多线程容许的状况下,一个线程中有多个指令,为了使线程切换能够复原到正确的地位,每个线程都具备各自独立的程序计数器,所以该区域是线程公有的。
如果执行的是Java办法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Native办法,计数器存储为空。这块区域是虚拟机标准中惟一没有OutOfMemoryError的区域。
作用
- 实现代码的流程管制
- 多线程容许的状况下,记录以后线程的执行地位,从而当线程被切换回来的时候指定该线程上次容许到哪儿了。
办法区(No-Heap)
办法区是一个形象的概念,JDK7及之前被称为“永恒代”,JDK8及当前被称为“元空间”,它用于存储虚拟机加载的类型信息、常量、动态变量(JDK7及之前,JDK8及之后就把动态变量与Class对象放到了堆中)、即时编译器编译(JIT)后的代码等数据,是各个线程的共享内存区域。尽管《Java虚拟机标准》中把办法区形容为堆的一个逻辑局部,然而它却有一个别名叫作“非堆”(Non-Heap),目标是与Java堆辨别开来。
JDK8之前,很多人把办法区又称为永恒代(Permanent Generation),或将两者一概而论,实质上是不对等的,因为仅仅是过后的HotSpot虚拟机设计团队把收集器的分代扩大至办法区,或者说应用永恒代来实现办法区而已,这样使得HotSpot的垃圾收集器可能像治理Java堆一样治理这部分内存,省去了专门为办法区编写内存治理代码的工作。然而这种设计更会导致Java利用更容易遇到内存溢出的问题(永恒代有-XX:MaxPermSize的下限,即便不设置也有默认大小,而像J9和JRockit只有没有触碰到过程可用内存的下限,就不会有问题),在JDK6的时候HotSpot团队就有放弃办法区,逐渐改为采纳本地内存(Native Memory)来实现办法区的打算,到了JDK7曾经把本来放在永恒代的字符串常量池、动态变量等移出,到了JDK8就齐全破除了永恒代的概念,改用JRockit、J9一样在本地内存中实现的元空间(Meta-space),把JDK7中永恒代残余的内容(次要是类型信息)全副移到元空间中。
办法区存储每个类信息,如:
- Classloader Reference
Run Time Constant Pool
- Numeric constants
- Field references
- Method References
- Attributes
Field data
Per field
- Name
- Type
- Modifiers
- Attributes
Method data
Per method
- Name
- Return Type
- Parameter Types (in order)
- Modifiers
- Attributes
Method code
Per method
- Bytecodes
- Operand stack size
- Local variable size
- Local variable table
Exception table
Per exception handler
- Start point
- End point
- PC offset for handler code
- Constant pool index for exception class being caught
- 类加载器参考 运行时常量池 数字常量 字段援用 办法援用 属性
- 畛域数据 每个字段 名字 类型 修饰符 属性
- 办法数据 每个办法 名字 返回类型 参数类型(按程序) 修饰符 *属性
- 办法代码 每个办法 字节码 操作数堆栈大小 局部变量大小 局部变量表 *例外表
在JDK8之前的HotSpot JVM,寄存这些“永恒的”区域叫做“永恒代(permanent generation)”。永恒代是一片间断的堆空间,在JVM启动前通过在命令行设置参数-XX:MaxPermSize来设定永恒代最大可调配的内存空间,默认大小为64M(64位的JVM默认是85M)。
办法区或永生代相干参数配置
-XX:PermSize=64MB
最小尺寸,初始调配-XX:MaxPermSize=256
最大容许调配尺寸- 按需分配
-XX:+CMSClassUnloadingEnabled
-XX:+CMSPermGenSweepingEnabled
设置垃圾不回收-server
选项下默认MaxPermSize为64MB
,-client
选项下默认MaxPermSize
为32MB
Java虚拟机标准堆办法区限度十分的宽松,可抉择不垃圾回收,以及不须要间断的内存和可扩大的大小。这个区域次要是针对于常量池的回收以及对类型的卸载,当办法区无奈调配到足够的内存的时候也会报OOM。
常量池
Class文件常量池
以下应用理论代码及反编译Class文件解说
反编译命令:javap -verbose StringTest.class
public class StringTest { private static String s1 = "static"; public static void main(String[] args) { String hello1 = new String("hell") + new String("o"); String hello2 = new String("he") + new String("llo"); String hello3 = hello1.intern(); String hello4 = hello2.intern(); System.out.println(hello1 == hello3); System.out.println(hello1 == hello4); }}
Classfile /E:/workspace/VariousCases/target/classes/cn/onenine/jvm/constantpool/StringTest.class Last modified 2021-8-3; size 1299 bytes MD5 checksum 338bd0034155ec3bf8d608540a31761c Compiled from "StringTest.java"public class cn.onenine.jvm.constantpool.StringTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Class #2 // cn/onenine/jvm/constantpool/StringTest #2 = Utf8 cn/onenine/jvm/constantpool/StringTest #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 s1 #6 = Utf8 Ljava/lang/String; #7 = Utf8 <clinit> #8 = Utf8 ()V #9 = Utf8 Code #10 = String #11 // static #11 = Utf8 static #12 = Fieldref #1.#13 // cn/onenine/jvm/constantpool/StringTest.s1:Ljava/lang/String; #13 = NameAndType #5:#6 // s1:Ljava/lang/String; #14 = Utf8 LineNumberTable #15 = Utf8 LocalVariableTable #16 = Utf8 <init> #17 = Methodref #3.#18 // java/lang/Object."<init>":()V #18 = NameAndType #16:#8 // "<init>":()V #19 = Utf8 this #20 = Utf8 Lcn/onenine/jvm/constantpool/StringTest; #21 = Utf8 main #22 = Utf8 ([Ljava/lang/String;)V #23 = Class #24 // java/lang/StringBuilder #24 = Utf8 java/lang/StringBuilder #25 = Class #26 // java/lang/String #26 = Utf8 java/lang/String #27 = String #28 // hell #28 = Utf8 hell #29 = Methodref #25.#30 // java/lang/String."<init>":(Ljava/lang/String;)V #30 = NameAndType #16:#31 // "<init>":(Ljava/lang/String;)V #31 = Utf8 (Ljava/lang/String;)V #32 = Methodref #25.#33 // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; #33 = NameAndType #34:#35 // valueOf:(Ljava/lang/Object;)Ljava/lang/String; #34 = Utf8 valueOf #35 = Utf8 (Ljava/lang/Object;)Ljava/lang/String; #36 = Methodref #23.#30 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V #37 = String #38 // o #38 = Utf8 o #39 = Methodref #23.#40 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #40 = NameAndType #41:#42 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #41 = Utf8 append #42 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #43 = Methodref #23.#44 // java/lang/StringBuilder.toString:()Ljava/lang/String; #44 = NameAndType #45:#46 // toString:()Ljava/lang/String; #45 = Utf8 toString #46 = Utf8 ()Ljava/lang/String; #47 = String #48 // he #48 = Utf8 he #49 = String #50 // llo #50 = Utf8 llo #51 = Methodref #25.#52 // java/lang/String.intern:()Ljava/lang/String; #52 = NameAndType #53:#46 // intern:()Ljava/lang/String; #53 = Utf8 intern #54 = Fieldref #55.#57 // java/lang/System.out:Ljava/io/PrintStream; #55 = Class #56 // java/lang/System #56 = Utf8 java/lang/System #57 = NameAndType #58:#59 // out:Ljava/io/PrintStream; #58 = Utf8 out #59 = Utf8 Ljava/io/PrintStream; #60 = Methodref #61.#63 // java/io/PrintStream.println:(Z)V #61 = Class #62 // java/io/PrintStream #62 = Utf8 java/io/PrintStream #63 = NameAndType #64:#65 // println:(Z)V #64 = Utf8 println #65 = Utf8 (Z)V #66 = Utf8 args #67 = Utf8 [Ljava/lang/String; #68 = Utf8 hello1 #69 = Utf8 hello2 #70 = Utf8 hello3 #71 = Utf8 hello4 #72 = Utf8 StackMapTable #73 = Class #67 // "[Ljava/lang/String;" #74 = Utf8 SourceFile #75 = Utf8 StringTest.java{ static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc #10 // String static 2: putstatic #12 // Field s1:Ljava/lang/String; 5: return LineNumberTable: line 11: 0 LocalVariableTable: Start Length Slot Name Signature public cn.onenine.jvm.constantpool.StringTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #17 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 10: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcn/onenine/jvm/constantpool/StringTest; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=5, locals=5, args_size=1 0: new #23 // class java/lang/StringBuilder 3: dup 4: new #25 // class java/lang/String 7: dup 8: ldc #27 // String hell 10: invokespecial #29 // Method java/lang/String."<init>":(Ljava/lang/String;)V 13: invokestatic #32 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 16: invokespecial #36 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 19: new #25 // class java/lang/String 22: dup 23: ldc #37 // String o 25: invokespecial #29 // Method java/lang/String."<init>":(Ljava/lang/String;)V 28: invokevirtual #39 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 31: invokevirtual #43 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 34: astore_1 35: new #23 // class java/lang/StringBuilder 38: dup 39: new #25 // class java/lang/String 42: dup 43: ldc #47 // String he 45: invokespecial #29 // Method java/lang/String."<init>":(Ljava/lang/String;)V 48: invokestatic #32 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 51: invokespecial #36 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 54: new #25 // class java/lang/String 57: dup 58: ldc #49 // String llo 60: invokespecial #29 // Method java/lang/String."<init>":(Ljava/lang/String;)V 63: invokevirtual #39 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 66: invokevirtual #43 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 69: astore_2 70: aload_1 71: invokevirtual #51 // Method java/lang/String.intern:()Ljava/lang/String; 74: astore_3 75: aload_2 76: invokevirtual #51 // Method java/lang/String.intern:()Ljava/lang/String; 79: astore 4 81: getstatic #54 // Field java/lang/System.out:Ljava/io/PrintStream; 84: aload_1 85: aload_3 86: if_acmpne 93 89: iconst_1 90: goto 94 93: iconst_0 94: invokevirtual #60 // Method java/io/PrintStream.println:(Z)V 97: getstatic #54 // Field java/lang/System.out:Ljava/io/PrintStream; 100: aload_1 101: aload 4 103: if_acmpne 110 106: iconst_1 107: goto 111 110: iconst_0 111: invokevirtual #60 // Method java/io/PrintStream.println:(Z)V 114: return LineNumberTable: line 13: 0 line 14: 35 line 15: 70 line 16: 75 line 17: 81 line 18: 97 line 20: 114 LocalVariableTable: Start Length Slot Name Signature 0 115 0 args [Ljava/lang/String; 35 80 1 hello1 Ljava/lang/String; 70 45 2 hello2 Ljava/lang/String; 75 40 3 hello3 Ljava/lang/String; 81 34 4 hello4 Ljava/lang/String; StackMapTable: number_of_entries = 4 frame_type = 255 /* full_frame */ offset_delta = 93 locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream, int ] frame_type = 79 /* same_locals_1_stack_item */ stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream, int ]}SourceFile: "StringTest.java"
运行时常量池
运行时常量池是办法区的一部分,Class文件中除了有类的版本、字段、办法、接口等信息外,还有一项信息是常量表(Constant Pool Table),用于寄存编译器生成的各种字面量和援用符号,这部分内容将在类加载后放到运行时常量池中。
运行时常量池绝对于Class文件常量池的另外一个特色是具备动态性,Java语言并不要求常量肯定只有编译期能力产生,也就是说并非预置入Class文件中常量池的内容能力进入办法区运行时常量池,运行期间也能够将新的常量放入运行时常量池,如String#intern()
办法。
既然运行时常量池是办法区的一部分,天然受到办法区的内存的限度,当常量池无奈再申请到内存时,就会抛出OutOfMemoryErro异样。
全局字符串常量池
HotSpot VM里,记录intered字符串的一个全局表叫做String Table,它实质上就是一个HashSet<String>,是一个纯运行时的构造,而且是惰性保护的。
只存储对java.lang.String实例的援用,而不存储理论的String对象,依据这个援用能够找到理论的String对象。
更多对于String与常量池相干的常识,独自开一篇文章记录,String与字符串常量池的恩怨情仇
虚拟机栈
虚拟机栈是每个Java办法的内存模型:每个办法被执行的时候都会创立一个"栈帧",用于存储局部变量表(包含参数)、操作栈、办法进口等信息。每个办法被调用到执行实现的过程,就对应着一个栈帧在虚拟机栈从入栈到出栈的过程。
平时说的栈个别指的是局部变量表局部。局部变量表寄存了编译期可知的各种Java虚拟机根本数据类型(boolean、byte、char、short、int、float、long、double)、对象援用(reference类型,它并不等同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或者其余与此对象相干的地位)和returnAddress类型(指向了一条字节码指令的地址),这些数据类型在局部变量表中以槽(slot)来示意,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。
局部变量表所须要的空间在编译期实现调配,当执行一个办法的时候,该办法须要在栈帧中调配多大的局部变量表的空间齐全是能够确定的,因而在办法运行的期间不会扭转局部变量表的大小,这里说的“大小”是指变量槽的数量,虚拟机真正应用多大的内存空间来实现一个变量槽是由具体的虚拟机实现自行决定的事件。
该区域就是咱们常说的Java内存中的栈区域,该区域的局部变量表存储的是根本类型、对象的援用类型,在对象的援用类型中存储的是指向对象的堆空间的地址。
该区域会呈现两种异样
- 当线程申请的栈深度超过虚拟机容许的深度,抛出StackOverflowError异样(递归!!!)
- 个别虚拟机的栈容量都是能够动静扩大的,当栈扩张时申请不多足够的内存,就会抛出OOM异样(HotSpot虚拟机的栈容量是不容许动静扩大的,所以HotSpot虚拟机上是不会因为虚拟机栈无奈扩大而导致OOM异样的----只有线程申请栈空间胜利了就不会呈现OOM,然而如果申请失败了,依然是会呈现OOM异样的)。
本地办法栈
本地办法栈(Native Method Stacks)与虚拟机栈施展的作用是十分类似的,其区别不过是虚拟机栈为虚拟机执行Java办法(字节码)服务,本地办法栈为虚拟机应用到的native办法分为,底层调用的是C或者C++的办法。
《Java虚拟机标准》中对本地办法栈中办法应用的语言、应用形式与数据结构没有任何强制规定,因而具体的虚拟机能够依据须要自在实现它,HotSpot虚拟机间接就把本地办法栈和虚拟机栈合二为一来应用,与虚拟机栈一样本地办法栈也会在栈深度溢出或者栈扩大失败时别离抛出StackOverflowError和OutOfMemoryError异样。
Java堆
Java堆(Java Heap)是Java虚拟机所治理的内存中最大的一块。Java堆是被所有线程共享的内存区域,在虚拟机启动的时候创立。
此内存区域的目标就是存储对象实例,简直所有的对象实例都在这里分配内存。从回收内存的角度看,因为古代垃圾收集器大部分都是基于分代实践设计的,所以Java堆中经常出现“新生代”、“老年代”、“永恒代”、“Eden空间”、“From Survivor空间”、“To Survivor空间”等名词,这些区域划分仅仅是局部垃圾收集器的独特个性或者设计格调,而非某个Java虚拟机的具体实现的固有内存布局,更不是《Java虚拟机标准》里对Java堆的进一步划分。后边讲到的G1垃圾收集器就不是基于分代实践设计的。
Java堆是线程共享的,它的目标是寄存对象实例。同时它也是GC所治理的次要区域,因而常被称为GC堆。依据虚拟机标准,Java堆能够存在物理上不间断的内存空间,就像磁盘空间逻辑上是间断的即可。它的内存大小能够设置为固定大小,也能够扩大。以后支流的虚拟机如HotSpot都能按扩大实现(通过设置 **-Xmx**
和-Xms
,默认堆内存大小为服务器内存的1/4),如果堆中没有内存实现实例调配,而且堆无奈扩大,则会报OOM谬误(OutOfMemoryError)。
新生代又分为:Eden空间、From Survivor
、To Survivor
空间,。进一步划分的目标是为了更好的回收内存或者更快的分配内存。
非堆(No-Heap)
- 办法区
- 字符串
- 代码缓存,用于缓存曾经被JIT编译器编译和缓存的本地代码
间接内存
间接内存并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机标准》中定义的内存区域,然而这部分内存也被频繁应用,而且也可能导致OOM。
在JDK 1.4中新退出了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区
(Buffer)的I/O形式,它能够应用Native函数库间接调配堆外内存,而后通过一个存储在Java堆外面的
DirectByteBuffer对象作为这块内存的援用进行操作。这样能在一些场景中显著进步性能,因为防止了
在Java堆和Native堆中来回复制数据。
本机内存间接内存调配不会受到Java堆大小的限度,然而既然是内存,必定会受到物理机内存的限度,当咱们通过-Xmx设置堆的最大内存时,不要忘了还有间接内存,如果堆内存设置过大,将会导致间接内存不够用,导致动静扩大时产生OOM。
间接内存的容量大小能够通过-XX:MaxDirectMemorySize参数来指定,如果不指定,则默认与Java堆的最大值(-Xmx)统一。
间接内存导致的OOM不会在Heap Dump文件中看到什么显著的异样,如果发现内存溢出后的Dump文件很小而程序中又间接或间接应用了DirectMemory(典型的间接应用就是NIO),那就能够思考重点检查一下间接内存方面的起因了。
本文由博客一文多发平台 OpenWrite 公布!