JVM的内存区域
一、JVM内存区域
运行时数据区
定义:java虚拟机在执行java程序的时候会把它所管理的内存划分为若干个不同的数据区域。
在JVM中,JVM的内存主要分为堆、方法区、虚拟机栈、本地方法栈、程序计数器等。同时,按照与线程的关系,也可以线程私有区(一个线程拥有单独一份内存区域)与线程共享区(被所有的线程的共享,只有一份).最后,还有一个直接内存(假如windows一共有8G内存,JVM虚拟化的时候使用了5G,还剩下的3G就可以称为直接内存)的概念,虽然它并不是运行时数据区,但是也会被使用。
现在来说一说JVM中那些名词到底是什么意思以及作用是什么?
虚拟机栈
定义:是线程运行java方法所需的数据、指令、返回地址。在实际的代码中,一个线程是可以运行多个方法的,如图
起一个 main 方法,在 main 方法运行中调用 A 方法,A 方法中调用 B 方法,B 方法中运行 C 方法。 我们把代码跑起来,线程 1 来运行这段代码, 线程 1 跑起来,就会有一个对应 的虚拟机栈,同时在执行每个方法的时候都会打包成一个栈帧。 比如 main 开始运行,打包一个栈帧送入到虚拟机栈,如图
C 方法运行完了,C 方法出栈,接着 B 方法运行完了,B 方法出栈、接着 A 方法运行完了,A 方法出栈,最后 main 方法运行完了,main 方法这个栈帧就 出栈了
虚拟机栈的作用:在 JVM 运行过程中存储当前线程运行方法所需的数据,指令、返回地址。
虚拟机栈的大小缺省为 1M(根据系统的不同默认的大小也不一样,Linux64位默认是1M),可用参数 –Xss 调整大小,如果我们不断的给虚拟机栈压入栈帧,但就是不出栈的话,那么就会抛出"*Error".
在上面的概念中,出现了栈帧的概念,可以如下理解。
栈帧:java方法被执行的时候,都会创建一个栈帧压入栈,方法执行完毕就会出栈.
栈帧包含4个区域:
1.局部变量表:用来存放局部的变量(方法中的变量),主要存放8大基础数据类型以及引用类型(可以理解为对象的地址)。
2.操作数栈:解释这个概念最好使用一个例子来解释。
操作系统
CPU + 缓存 + 主内存
JVM是模拟版的操作系统
JVM执行引擎 + 操作数栈 + 栈、堆
CPU约等于JVM执行引擎 、缓存约等于操作数栈、主内存约等于栈、堆
看下面的例子:
操作数栈本质上是 JVM 执行引擎的一个工作区,也就是方法在执行,才会对操作数栈进行操作,如果代码不不执行,操作数栈其实就是空的。
3.动态连接:这个以后在说.
4.返回地址
程序计数器
简单理解成它用来存储字节码指令的行号。
解释:如图
将一段程序进行反编译之后呢,字节码的前面可以看到0,1,2,3,… 又因为java是多线程的,也就是说当一个线程在执行到某一个字节码的时候恰好另一个线程执行了别的程序,当轮到刚才的线程执行程序时,要直到它已经运行到了哪里,说白了,应该时起到一个标记的作用
本地方法栈
java中有许多native关键字修饰的方法,这些方法并不是java写的,而是由c或者c++写的,本地方法栈就是负责native方法的,现在看,虚拟机栈负责java写的方法,本地方法栈负责native方法,所以可以将本地方法栈理解为与虚拟机栈功能类似。
方法区
方法区主要是用来存放已被虚拟机加载的类相关信息,包括类信息、静态变量、常量、运行时常量池、字符串常量池等。 方法区是 JVM 对内存的“逻辑划分”,在 JDK1.7 及之前很多开发者都习惯将方法区称为“永久代”,是因为在 HotSpot 虚拟机中,设计人员使用了永久 代来实现了 JVM 规范的方法区。在 JDK1.8 及以后使用了元空间来实现方法区。 JVM 在执行某个类的时候,必须先加载。在加载类(加载、验证、准备、解析、初始化)的时候,JVM 会先加载 class 文件,而在 class 文件中除 了有类的版本、字段、方法和接口等描述信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用。 字面量包括字符串(String a=“b”)、基本类型的常量(final 修饰的变量),符号引用则包括类和方法的全限定名(例如 String 这个类,它的全限定名 就是 Java/lang/String)、字段的名称和描述符以及方法的名称和描述符。
符号引用
一个 java 类(假设为 People 类)被编译成一个 class 文件时,如果 People 类引用了 Tool 类,但是在编译时 People 类并不知道引用类的实际内存地址,因 此只能使用符号引用来代替。
而在类装载器装载 People 类时,此时可以通过虚拟机获取 Tool 类的实际内存地址,因此便可以既将符号 org.simple.Tool 替换为 Tool 类的实际内存地址, 及直接引用地址。
常量池与运行时常量池
而当类加载到内存中后,JVM 就会将 class 文件常量池中的内容存放到运行时的常量池中;在解析阶段,JVM 会把符号引用替换为直接引用(对象 的索引值)。 例如,类中的一个字符串常量在 class 文件中时,存放在 class 文件常量池中的;在 JVM 加载完类之后,JVM 会将这个字符串常量放到运行时常量 池中,并在解析阶段,指定该字符串对象的索引值。运行时常量池是全局共享的,多个类共用一个运行时常量池,class 文件中常量池多个相同的字符串 在运行时常量池只会存在一份。
元空间
后面在说
堆
堆是 JVM 上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。 堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。堆一般设置成可伸缩的。 随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java 中,就叫作 GC(Garbage Collection)。 那一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?这和两个方面有关:对象的类型和在 Java 类中存在的位置。 Java 的对象可以分为基本数据类型和普通对象。 对于普通对象来说,JVM 会首先在堆上创建对象,然后在其他地方使用的其实是它的引用。比如,把这个引用保存在虚拟机栈的局部变量表中。 对于基本数据类型来说(byte、short、int、long、float、double、char),有两种情况。 当你在方法体内声明了基本数据类型的对象,它就会在栈上直接分配。其他情况,都是在堆上分配。
堆大小参数: -Xms:堆的最小值; -Xmx:堆的最大值; -Xmn:新生代的大小; -XX:NewSize;新生代最小值; -XX:MaxNewSize:新生代最大值; 例如- Xmx256m