JVM的组成

运行时数据区-总览
Java虚拟机在运行Java程序过程中管理的内存区域,称之为运行时数据区。
《Java虚拟机规范》中规定了每一部分的作用

运行时数据区-应用场景
Java的内存分成哪几部分?
Java内存中哪些部分会内存溢出?
JDK7 和JDK8中在内存结构上的区别是什么?
内存调优

一、运行时数据区-程序计数器
程序计数器(Program Counter Register)也叫PC寄存器,每个线程会通过程序计数器记录当前要执行的字节码指令的地址。

作用
1、可以控制程序指令的进行,实现分支、跳转、异常等逻辑。
2、在多线程执行情况下,Java虚拟机需要通过程序计数器记录CPU切换前解释执行到哪一句指令并继续解释运行。
程序计数器在运行时会出现内存溢出吗?
内存溢出指的是程序在使用某一块内存区域时,存放的数据需要占用的内存大小超过了虚拟机能提供的内存上限。
因为每个线程只存储一个固定长度的内存地址,程序计数器是不会发生内存溢出的。
程序员无需对程序计数器做任何处理。
二、栈
Java虚拟机栈和本地方法栈。:Java虚拟机栈存储了Java方法调用时的栈帧,而本地方法栈存储的是native本地方法的栈帧。
Java虚拟机栈用来保存java中实现的方法,每次请求方法都会往栈里进行保存。而本地方法栈存储的是native本地方法的栈帧。
采用栈的数据结构来管理方法调用中的基本数据,先进后出(First In Last Out),每一个方法的调用使用一个栈帧(Stack Frame)来保存。
Java虚拟机栈随着线程的创建而创建,而回收则会在线程的销毁时进行。由于方法可能会在不同线程中执行,每个线程都会包含一个自己的虚拟机栈。

局部变量表的作用是在方法执行过程中存放所有的局部变量。编译成字节码文件时就可以确定局部变量表的内容。
操作数栈是栈帧中虚拟机在执行指令过程中用来存放中间数据的一块区域。他是一种栈式的数据结构,如果一条指令将一个值压入操作数栈,则后面的指令可以弹出并使用该值。
在编译期就可以确定操作数栈的最大深度,从而在执行时正确的分配内存大小。
帧数据当前类的字节码指令引用了其他类的属性或者方法时,需要将符号引用(编号)转换成对应的运行时常量池中的内存地址。动态链接就保存了编号到运行时常量池的内存地址的映射关系。
方法出口指的是方法在正确或异常结束时,当前栈帧会被弹出,同时程序计数器应该指向上一个栈帧中的下一条指令的地址。所以在当前栈帧中,需要存储此方法出口的地址。
异常表存放的是代码中异常的处理信息,包含了异常捕获的生效范围以及异常发生后跳转到的字节码指令位置。
Java虚拟机栈-栈内存溢出
Java虚拟机栈如果栈帧过多,占用内存超过栈内存可以分配的最大大小就会出现内存溢出。
Java虚拟机栈内存溢出时会出现StackOverFlowError的错误。

Java虚拟机栈-默认大小
如果我们不指定栈的大小,JVM将创建一个具有默认大小的栈。大小取决于操作系统和计算机的体系结构。
要修改Java虚拟机栈的大小,可以使用虚拟机参数 -Xss
语法: -Xss栈大小
单位: 字节(默认,必须是1024的倍数)、k或者K(KB)、m或者M(MB)、g或者G(GB)。
-Xss1024k,
Java虚拟机栈-注意事项
1、HotSpot JVM对栈大小的最大值和最小值有要求:
比如测试如下两个参数:
-Xss1k
-Xss1025m
Windows(64位)下的JDK8测试最小值为180k,最大值为1024m。
2、局部变量过多、操作数栈深度过大也会影响栈内存的大小。
一般情况下,工作中即便使用了递归进行操作,栈的深度最多也只能到几百,不会出现栈的溢出。所以此参数可以手动指定为-Xss256k节省内存。
本地方法栈
Java虚拟机栈存储了Java方法调用时的栈帧,而本地方法栈存储的时native本地方法的栈帧。
在HotSpot虚拟机中,Java虚拟机栈和本地方法栈实现上使用了同一个栈空间。本地方法栈会在栈内存上生成一个栈帧,临时保存方法的参数同时方便出现异常时也把本地方法的栈信息打印出来。

三、堆
一般Java程序中堆内存是空间最大的一块内存区域,创建出来的对象都存在堆上。
栈上的局部变量表中,可以存放堆上对象的引用。静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间共享。
堆空间有三个需要关注的值,used、total、max
used指的是当前已使用的堆内存,total是java虚拟机已经分配的可用堆内存,max是java虚拟机可以分配的最大堆内存。

arthas堆内存相关的功能
堆内存used total max 三个值可以通过dashboard命令看到
手动指定刷新频率(不指定默认5秒一次):dashboard -i刷新频率(毫秒)
堆-设置大小
要修改堆的大小,可以试用虚拟机参数-Xmx(max最大值)和-Xms(初始的total)
语法:-Xmx值 -Xms值
单位:字节(默认,必须是1024的倍数)、k或者K(KB)、m或者M(MB)、g或者G(GB)
限制:Xmx必须大于 2 MB,Xms必须大于1MB
Java服务端程序开发时,建议将-Xmx 和-Xms设置为相同的值,这样在程序启动之后可以使用的总内存就是最大内存,而无需向java虚拟机再次申请,减少了申请并分配内存时间上的开销,同时也不会出现内存过剩之后堆收缩的情况。
-Xmx具体设置的值与实际的应用程序运行环境有关。
四、方法区
方法区是线程共享区,方法区存放基础信息的位置,线程共享,主要包含三部分内容:
类的元信息(保存所有类的基本信息)、运行时常量池(保存了字节码文件中的常量池内容)、字符串常量池(保存了字符串常量)。

1、方法区是用来存储每个类的基本信息(元信息),一般称之为InstanceKlass对象。在类的加载阶段完成。

2、方法区除了存储类的元信息之外,还存放了运行时常量池。常量池中存放的是字节码中的常量池内容。
字节码文件中通过编号查表的方式找到常量,这种常量池称为静态常量池。当常量池加载到内存中后,可以通过内存地址快速的定位到常量池中的内容,这种常量池称为运行时常量池。

方法区是《Java虚拟机规范》中设计的虚拟概念,每款Java虚拟机在实现上都各不相同。Hotspot设计如下:
JDK7及之前的版本将方法区放在堆区域中的永久代空间,堆的大小由虚拟机参数控制。
JDK8及之后的版本将方法区存放在元空间中,元空间位于操作系统维护的直接内存中,默认情况下只要不超过操作系统承受的上限,可以一致分配。

arthas中查看方法区
使用memory打印出内存情况,JDK7及之前的版本查看ps_perm_gen属性,永久代
JDK8及之后的版本查看metaspace属性。 元空间
实验-模拟方法区的溢出
通过ByteBuddy框架,动态生成字节码数据,加载到内存中。通过死循环不停地加载到方法区,观察方法区是否会出现内存溢出的情况。分别在JDK7和JDK8上运行上述代码。
ByteBuddy是一个基于Java的开源库,用于生成和操作Java字节码。
总结,方法区(Method Area)溢出
JDK7上运行大概十几万次,就出现了错误,在JDK8上运行百万次,程序都没有出现任何错误,但是内存会直线升高。说明JDK7和JDK8在方法区的存放上,采用了不同的设计。
JDK7 将方法区存放在堆区域中的永久代空间,堆的大小由虚拟机参数-XX:MaxPermSize=值来控制。
JDK8将方法区存放在元空间中,元空间位于操作系统维护的直接内存中,默认情况下只要不超过操作系统承受的上限,可以一直分配。可以使用-XX:MaxMetaspaceSize=值将元空间最大大小进行限制。

3、字符串常量池,字符串常量池存储在代码中定义的常量字符串内容。比如”123“,这个123就会被放入字符串常量池。

字符串常量池和运行时常量池有什么关系?
早期设计时,字符串常量池是属于运行时常量池的一部分,他们存储的位置也是一致的。后续做出了调整,将字符串常量池和运行时常量池做了拆分。

JDK8之后,字符串常量池还在堆中,运行时常量池存在原空间Metaspace中。
例子如下:
d存放的是堆内存中字符串地址
c 存放的是字符串常量池

这种情况都是来自于字符串常量池,所以 c==d

静态变量的存储
JDK6及之前的版本中,静态变量是存放在方法区中的,也就是永久代。

JDK7及之后的版本中,静态变量存放在堆中的Class对象中,脱离了永久代。

五、直接内存
直接内存 并不在《Java虚拟机规范》中存在,所以并不属于Java运行时的内存区域。在JDK1.4中引入了NIO机制,使用了直接内存,主要为了解决一下两个问题:
1、Java堆中的对象如果不再使用要回收,回收时会影响对象的创建和使用。
2、IO操作比如读文件,需要先把文件读入直接内存(缓冲区)再把数据复制到Java堆中。
现在直接放入直接内存即可,同时Java堆上维护直接内存的引用,减少了数据复制的开销,写文件也是类似的思路。
总结
1、运行时数据区分成几个部分,每一部分的作用是什么?

程序计数器:每个线程会通过程序计数器记录当前要执行的字节码指令的地址,程序计数器可以控制程序指令的进行,实现分支、跳转、异常等逻辑。
Java虚拟机栈、本地方法栈::虚拟机栈采用栈的数据结构来管理方法调用中的基本数据(局部变量、操作数等),每一个方法的调用使用一个栈帧来保存。
方法区:方法区中主要存放的是类的元信息,同时还保存了常量池。
堆:
2、不同JDK版本之间运行时数据区域的区别是什么?
JDK6,字符串常量池,存放在永久代。

JDK7,字符串常量池,被存放在堆内存中

JDK8,永久代被去掉,改为元空间

2230

被折叠的 条评论
为什么被折叠?



