JVM内存运行时数据区域( Run-Time Data Areas)
文档:
可以参考着文档来理解
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虚拟机定义了几部分运行时数据区域用于执行程序。某些数据区域在虚拟机启动的时候创建,在虚拟机退出的时候销毁。某些数据区域属于每个线程,所以线程独享的数据区域会在线程创建的时候创建,线程结束的时候销毁。
不过文章没有图,比较抽象的说话
那么我们后面根据文档画个图出来吧,先把文档看完吧
有哪些分类呢?
The pc Register
The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6) for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the specific platform.
Java虚拟机支持多线程执行,每一个线程都有它自己的计数器。在每个时间片,每一个java虚拟机线程执行着单独一个方法,也就是当前线程所执行的方法。如果那个方法不是本地方法,那么计数器就会包含当前执行的方法地址。如果当前方法是本地方法,那么计数器里的值会是undefined。虚拟机的计数器有足够大的空间来容纳所返回的地址或者平台所执行到的地方。
从这文档中,我们可以得知道计数器的作用:
- 保存当前线程所执行方法的位置(地址),如果是本地方法,那么就会是undefined
特点:
- 线程私有,也就是每个线程有自己独立的计数器
- 空间足够记录地址,不会出现内存溢出的异常
Java Virtual Machine Stacks
Java虚拟机栈
a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
Java虚拟机栈是方法调用和返回的一部分。因为Java虚拟机不会直接操作,除非是栈帧的入栈和出栈,栈帧会被大量分配。Java虚拟机的栈内存不一定要连续的。
In the First Edition of The Java® Virtual Machine Specification, the Java Virtual Machine stack was known as the Java stack.
在第一版Java虚拟机规范中,大家所说的Java栈指的就是Java虚拟机栈。
This specification permits Java Virtual Machine stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the Java Virtual Machine stacks are of a fixed size, the size of each Java Virtual Machine stack may be chosen independently when that stack is created.
这套规范中规定了Java虚拟机的栈大小可以是固定的,或者是动态扩展的,也就是动态计算所得的。如果Java虚拟机栈的大小是固定的,每一个栈创建的时候,大小都是独立设定的。
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of Java Virtual Machine stacks, as well as, in the case of dynamically expanding or contracting Java Virtual Machine stacks, control over the maximum and minimum sizes.
Java虚拟机可以让程序或者用户设置栈的最大空间和最小空间的值
The following exceptional conditions are associated with Java Virtual Machine stacks:
以下的异常跟栈有关:
- If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
如果线程所使用的栈内存超出了所允许的,就会抛出栈溢出异常(StackOverflowError)
- If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.
如果Java虚拟机栈可以动态地扩展,在尝试扩展时,如果内存不足的情况下,动态伸缩会无效。或者给新的线程去初始化化栈空间,则会抛出超出内存大小的错误(OutOfMemoryError),也就是我们常说的内存溢出。
由上我们可以知道:
- 栈的大小可以设定,可以动态扩展,我们在执行java程序的时候,可以通过参数来设定
如果是我们手动执行Java程序,我们是不是java xxx呀
所以是在java上加参数即可
IDE也是可以配置的
具体的参数内容可以看这里
设置栈的大小,有例子和说明了
- 栈有可能抛出的异常
- 内存溢出异常,也就是所定的size不够用了
- 超出内存大小异常,也就是所申请的内存超出了内存的大小了,也就是内存溢出
- 栈数据的内存安全问题,栈是线程独享的
- 如果是站内的数据,则是线程安全的
- 如果是返回给外部使用,或者由外部传进来的数据,则不一定线程安全
所以这里就有个问题了,怎么排查异常,后面再分出专题去写吧。
另外这里的栈帧其实就是方法块,里面有可能做一些耗时操作,占用CPU的资源过来,也有可能出现死锁之类的。
这些怎么分析,在android里有ANR异常,也会产生traces.txt文件,其实在java里也一样的。
先是获取到进程--->获取到当前进程所有的线程--->dump一个快照出来,再结合代码去分析。
Heap
堆
客家话里的堆是不是屁股的意思,哈哈!上学的时候常听同学说,[捂脸]
先看原文档吧
The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
Java虚拟机里的堆内存是所有线程共享的。堆内存是给对象和数组分配的内存。
The heap is created on virtual machine start-up. Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated. The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor's system requirements. The heap may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger heap becomes unnecessary. The memory for the heap does not need to be contiguous.
当虚拟机启动的时候,对内存就创建了。这些内存会被gc管理器回收;在某个时间就会回收。Java虚拟机没有什么特定的空间管理系统,所以呢,写JVM的人,可以自己去选择或者写一套自己的管理方式。堆内存的大小可以是固定的,可以是动态的,可以是不连续的。
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the heap, as well as, if the heap can be dynamically expanded or contracted, control over the maximum and minimum heap size.
Java虚拟机允许用户设置堆的初始大小,可以通过设置最大空间和空间最小值来控制堆内存动态伸缩。
The following exceptional condition is associated with the heap:
以下异常跟堆相关:
If a computation requires more heap than can be made available by the automatic storage management system, the Java Virtual Machine throws an OutOfMemoryError.
如果所需要的大小超出了所给的空间,那么java虚拟机就会抛出一个内存溢出的异常。
堆内存
从上面我们可得知道
- 堆用存放实例和数组的
- 堆是线程共享的
- 堆是在虚拟机开启的时候初始化/分配的
- 堆内存的大小一样可以通过参数设置,如果你想它动态伸缩的话,设置最大值和最小值即可
设置堆内存的初始大小,或者最大值,最小值可以参考上图的参数。
堆内存如果出现了溢出的问题,我们结合Log和代码去分析就好。
Method Area
方法区
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.
Java虚拟机的方法区是线程共享的。代码区就是存储编译后的代码,比如说类的信息,常量池,字段,方法之类的数据。也包括一些用于对象实例初始化的方法。
这里我就反编译一下给大家看看,.class文件有什么内容。
简单来一个Hello world吧!
源代码
public class Test{
public static void main(String[] args){
System.out.println("hello world");
}
}
编译-->得到了.class文件
然后反编译:
这种反编译出来的跟我们的IDE反编译出来的是一样的
我们加个参数-v 查看详细信息
这样子,我们就可以看到:
Classfile /C:/Users/TrillGates/Desktop/demo/Test.class
Last modified 2020-4-25; size 413 bytes
MD5 checksum 3bdd27cc59148b93fb3dc3f1b4a43e8b
Compiled from "Test.java"
public class Test
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 world
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // Test
#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 Test.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 hello world
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 Test
#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 Test();
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: 0
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 hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
}
SourceFile: "Test.java"
这里面呢,就有文档里说到的pre-class structures了
还有Constant pool,自己去对吧。
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
方法区是在虚拟机启动的时候创建的。虽然说方法 区逻辑上是堆的一部分,但通常来说不会由gc去回收它。此规范不会方法区里的代码。方法区的大小可以固定,或者动态伸缩。内存也不是必须是连续的。
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
Java虚拟机可以让用户或者程序设置方法区的初始大小,或者设置方法区大小的最大值和最小值。
The following exceptional condition is associated with the method area:
相关的异常:
If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.
内存溢出
通过这些文档,我们里了解到了:
- 方法区是线程共享的
- 方法区的大小不够用了,会抛出内存溢出的异常
- 方法区存放已经编译的代码
Run-Time Constant Pool
运行时常量池
前面我们在方法区里有一个常量池,这个是在.class文件里的
而里面的常量池,加载时会被载入到运行时常量池里。
A run-time constant pool is a per-class or per-interface run-time representation of the constant_pool table in a class file (§4.4). It contains several kinds of constants, ranging from numeric literals known at compile-time to method and field references that must be resolved at run-time. The run-time constant pool serves a function similar to that of a symbol table for a conventional programming language, although it contains a wider range of data than a typical symbol table.
运行时常量池是根据字节码文件里的常量池表加载的类或者接口。它包括了几类的常量:数字、文字、方法、字段、引用。运行时常量池就像一个符号一表一样,给编程语言提供转换的功能。它包含的范围很广,比基本数据类型还大。
Each run-time constant pool is allocated from the Java Virtual Machine's method area (§2.5.4). The run-time constant pool for a class or interface is constructed when the class or interface is created (§5.3) by the Java Virtual Machine.
每一个运行时常量表从方法区中分配。运行时常量池在类或者接口创建被虚拟机的创建的时候创建。
The following exceptional condition is associated with the construction of the run-time constant pool for a class or interface:
相关异常:
When creating a class or interface, if the construction of the run-time constant pool requires more memory than can be made available in the method area of the Java Virtual Machine, the Java Virtual Machine throws an OutOfMemoryError.
内存溢出
See §5 (Loading, Linking, and Initializing) for information about the construction of the run-time constant pool.
由上可知:
- 运行时常量池在类/接口创建的时候而分配
- 常量池在方法区里
其实这里面还有一个StringTable的概念,俗称‘串池’,字符串池嘛。
在1.6的版本它在常量池里
但是这个用得了太频繁了,到了1.8以后,放在了堆内存里。因为方法区指向的是在本地内存里的元空间
我们后面可以开专题去验证一下即可。了解,要是你的面试官能问到这里,你工资往多要就对了。
Native Method Stacks
本地方法栈
本地方法就是native方法,比如说我们的Object里的clone,hasCode之类的。
换句话说,就是JNI接口方法,再往下就是C/C++实现了
An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.
Java虚拟机通常会使用栈,俗称C栈来支持本地方方法的实现。本地方法栈也会被Java虚拟机用C语言写的解析器调用。Java虚拟机不通过本地方法栈的话是无法加载本地方法的。如果想加载的话,得通过本地方法栈。本地方法栈会在每个线程创建的时候被分配。
This specification permits native method stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the native method stacks are of a fixed size, the size of each native method stack may be chosen independently when that stack is created.
规范规定,本地方法的尺寸是可以固定的,也可以动态地伸缩。如果本地方法栈的空间是固定的,每个线程在创建本地方法栈是,可以独立地设置。
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the native method stacks, as well as, in the case of varying-size native method stacks, control over the maximum and minimum method stack sizes.
可以在初始化的时候设置本地房发栈的大小。这跟前面的一样的,我怀疑写文档的人是复制粘贴的。
The following exceptional conditions are associated with native method stacks:
相关异常:
If the computation in a thread requires a larger native method stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
栈溢出异常
If native method stacks can be dynamically expanded and native method stack expansion is attempted but insufficient memory can be made available, or if insufficient memory can be made available to create the initial native method stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.
内存溢出异常
本地方法栈跟Java的栈一样,都是线程独享的。
有异常前面提到了
可以设置它的size
图是如何的呢?
总结
我感觉我翻译得不咋滴,哈哈。看能看懂,但是说明中文就不知道怎么说更好。因为英语里有很多定语,翻译了前面后面发现要调整一下。
知道这几个内存区的特点
有什么用,就差不多了。至于后面的内存溢出,或者栈溢出,我们再去分析如何解决。