深入了解JVM(一)

JVM内存区域-运行时数据区


在 JVM 中, JVM 内存主要分为堆、 程序计数器、 方法区、 虚拟机栈和本地方法栈等。

同时按照与线程的关系也可以这么划分区域:
线程私有区域: 一个线程拥有单独的一份内存区域。
线程共享区域: 被所有线程共享, 且只有一份。
这里还有一个直接内存, 这个虽然不是运行时数据区的一部分, 但是会被频繁使用。 你可以理解成没有被虚拟机化的操作系统上的其他内存
在这里插入图片描述

栈区
虚拟机栈

在 JVM 运行过程中存储当前线程运行方法所需的数据, 指令、 返回地址。虚拟机栈是基于线程的。

栈帧: 在每个 Java 方法被调用的时候, 都会创建一个栈帧, 并入栈。 一旦方法完成相应的调用, 则出栈。
栈帧大体都包含四个区域: 局部变量表、 操作数栈、 动态连接、 返回地址。
局部变量表是一个32位的主要存放八大基础数据类型的表,如果是对象我们存放一个引用地址;造作数栈是用来执行操作的,动态链接是实现java多态的,方法的底层调用由字节码完成,虚方法中动态分派的调用需要动态链接来完成;返回地址是方法正常返回的完成出口偏移量。

本地方法栈

用于管理本地方法的调用。 但本地方法并不是
用 Java 实现的, 而是由 C 语言实现的,就是用来调用native方法

程序计数器

程序计数器是一块很小的内存空间, 主要用来记录各个线程执行的字节码的地址, 例如, 分支、 循环、 跳转、 异常、 线程恢复等都依赖于计数器。
由于 Java 是多线程语言, 当执行的线程数量超过 CPU 核数时, 线程之间会根据时间片轮询争夺 CPU 资源。 如果一个线程的时间片用完了, 或者是其它原因导致这个线程的 CPU 资源被提前抢夺, 那么这个退出的线程就需要单独的一个程序计数器, 来记录下一条运行的指令。程序计数器也是 JVM 中唯一不会 OOM(OutOfMemory)的内存区域。

方法区

方法区(Method Area) 是可供各条线程共享的运行时内存区域。 它存储了每一个类的结构信息, 例如运行时常量池(Runtime Constant Pool)
字段和方法数据、 构造函数和普通方法的字节码内容、 还包括一些在类、 实例、 接口初始化时用到的特殊方法。方法区与堆空间类似, 也是一个共享内存区, 所以方法区是线程共享的。 假如两个线程都试图访问方法区中的同一个类信息, 而这个类还没有装入
JVM, 那么此时就只允许一个线程去加载它, 另一个线程必须等待。在 JDK1.7 及之前很多开发者都习惯将方法区称为“永久代”,JDK1.8 及以后使用了元空间来实现方法区。元空间的存储位置是本地内存。

Class 常量池(静态常量池)

存放编译期间生成的各种字面量(比如: String a=“b” , 这里“b”就是字符串字面量)和符号引用(JAVA 在编译的时候一个每个 java 类都会被编译成一个 class
文件, 但在编译的时候虚拟机并不知道所引用类的实际地址, 就用符号引用来代替,而在类的解析阶段就是为了把这个符号引用转化成为真正的地址的阶段)

运行时常量池

包括了若干种不同的常量:
从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。 运行时常量池是在类加载完成之后,将Class常量池中的符号引用值转存到运行时常量池中,类在解析之后,将符号引用替换成直接引用。

字符串常量池

以JDK1.8 为例,字符串常量池是存放在堆中。
在这里插入图片描述
String对象是对char数组进行了封装实现的对象,主要有2个成员变量:char数组,hash值。String 类被 final 关键字修饰了, 而且变量 char 数组也被final修饰了。final修饰代表该类不可继承, 而 char[]被final+private修饰,代表了String对象不可被更改,这个特性叫作 String 对象的不可变性。保证了String 对象的安全性,hash值不变保证了string的唯一性,可以实现字符串常量池。

String 的创建方式及内存分配的方式

String str = “abc”
JVM 首先会检查该对象是否在字符串常量池中, 如果在, 就返回该对象引用,否则新的字符串将在常量池中被创建。这种方式可以减少同一个值的字符串对象的重复创建, 节约内存。

String str = new String(“abc”);
在编译类文件时, "abc"常量字符串将会放入到常量结构中, 在类加载时,“abc"将会在常量池中创建; 其次, 在调用 new 时, JVM 命令将会调用 String
的构造函数, 同时引用常量池中的"abc” 字符串, 在堆内存中创建一个 String 对象; 最后, str 将引用 String 对象。

String 的 intern 方法, 如果常量池中有相同值, 就会重复使用该对象, 返回对象引用。

1、 new String() 会在堆内存中创建一个 a 的 String 对象, "king"将会在常量池中创建
2、 在调用 intern方法之后,会去常量池中查找是否有等于该字符串对象的引用, 有就返回引用。
3、 调用 new String() 会在堆内存中创建一个 b 的 String 对象。
4、 在调用 intern方法之后,会去常量池中查找是否有等于该字符串对象的引用, 有就返回引用。所以 a和b引用的是同一个对象


堆是JVM 上最大的内存区域, 我们申请的几乎所有的对象, 都是在这里存储的。 我们常说的垃圾回收, 操作的对象就是堆。-Xms: 堆的最小值;
-Xmx: 堆的最大值;
-Xmn: 新生代的大小。

直接内存( 堆外内存)

直接内存主要是通过 DirectByteBuffer 申请的内存, 可以使用参数“MaxDirectMemorySize” 来限制它的大小。
其他堆外内存, 主要是指使用了 Unsafe 或者其他 JNI 手段直接直接申请的内存。


JVM内存的整体流程

JVM 在操作系统上启动, 申请内存, 先进行运行时数据区的初始化, 然后把类加载到方法区, 最后执行方法。
方法的执行和退出过程在内存上的体现上就是虚拟机栈中栈帧的入栈和出栈。
同时在方法的执行过程中创建的对象一般情况下都是放在堆中, 最后堆中的对象也是需要进行垃圾回收清理的。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值