JVM的简化架构和运行时数据区
JVM的简化架构
运行时数据区
包括:PC寄存器,Java虚拟机栈,Java堆,方法区,运行时常量池,本地方法栈等
PC寄存器(程序计数器)
PC(Program Counter)寄存器说明:
- 每个线程拥有一个PC寄存器,是线程私有的,用来存储指向下一条指令的地址
- 在创建线程的时候,创建相应的PC寄存器
- 执行本地方法时,PC寄存器的值为undefined
- PC寄存器是一块较小的内存空间,是唯一一个在JVM规范中没有规定OutOfMemoryError的内存区域
Java栈
- 栈由一系列帧(Frame)组成(因此Java栈也叫做帧栈),是线程私有的
- 帧用来保存一个方法的局部变量,操作数栈(Java没有寄存器,所有参数传递使用操作数栈),常量池指针,动态链接,方法返回值等
- 每一次方法调用创建一个帧,并压栈,退出方法的时候,修改栈顶指针就可以把栈帧中的内容销毁
- 局部变量表存放了编译期可知的各种基本数据类型和引用类型,每个slot存放32位的数据,long,double占两个槽位
- 栈的优点:存取速度比堆快,仅次于寄存器
- 栈的缺点:存在栈中的数据大小,生存期是在编译器决定的,缺乏灵活性
Java堆
- 用来存放应用系统创建的对象和数组,所有线程共享Java堆
- GC主要就管理堆空间,对分代GC来说,堆也是分代的
- 堆的优点:运行期动态分配内存大小,自动进行垃圾回收;堆的缺点:效率相对较慢
方法区
- 方法区是线程共享的,通常用来保存装载的类的结构信息
- 通常和元空间关联在一起,但具体的跟JVM实现和版本有关
- JVM规范把方法区描述为堆的一个逻辑部分,但它有一个别名称为Non-heap(非堆),应是为了与Java堆区分开
运行时常量池
- 运行时常量池是Class文件中每个类或接口的常量池表,在运行期间的表示形式,通常包括:类的版本,字段,方法,接口等信息。
- 运行时常量池在方法区分配
- 通常在加载类和接口到JVM后,就创建相应的运行时常量池
本地方法栈
- 在JVM中用来支持native方法执行的栈就是本地方法栈
栈,堆,方法区交互关系
运行期间首先有个栈,栈内存放我们的局部变量表里面的一些变量(A b),当然包括一些引用类型(user)。那么引用类型需要指向某个对象实例,对象实例肯定不在栈上存放,对象实例是存放在堆内的。那堆内创建了自己的对象,比如User对象,User对象包括两类数据:1.User类的元数据信息(这个元数据信息指向方法区,是一个句柄,一个引用)。2.实例数据。由上图看出,栈上的user变量指向了堆内的User对象,而堆内的User对象持有了User类的元数据信息。堆内的元数据信息就会去方法区去找User类的类定义,字段,方法。
Java堆内存模型
Java堆内存的概述
- Java堆用来存放应用系统创建的对象和数组,所有线程共享Java堆
- Java堆是在运行期动态分配内存大小,自动进行垃圾回收
- Java垃圾回收(GC)主要就是回收堆内存,对分代GC来说,堆也是分代的
Java堆的结构
由上图看出:目前Java堆是分代的,分成两代。一个是新生代(Young Generation),由Eden Space,From Space 和 To Space共同组成。另外一个是老年代(Old Generation),由Tenured Space组成。把新创建的对象(Init Obj Alloc)放在Eden Space中,From Space 和 To Space 是存活区。老年代存放的是多次垃圾回收收不掉的一些内容,或者判断这些东西需要保存下来的一些内容,会从To Space 转到Tenured Space。另外图上的Survivor Ratio设置的是Eden Space和存活区的比列
具体看一下Java堆知识
- 新生代用来存放新分配的对象;新生代中经过垃圾回收,没有回收掉的对象,被复制到老年代
- 老年代存储对象比新生代存储对象的年龄大得多
- 老年代存储一些大对象
- 整个堆得大小 = 新生代 + 老年代
- 新生代 = Eden + 存活区
- 从前的持久代,用来存放Class,Method等元信息的区域,从JDK8开始去掉了,取而代之的是元空间(MetaSpace),元空间并不在虚拟机里面,而是直接使用本地内存
堆内对象的内存布局
- 对象在内存中存储的布局(这里以HotSpot虚拟机为例来说明),分为:对象头,实例数据和对齐填充
- 对象头,包含两个部分:
- Mark Word:存储对象自身的运行数据,如:HashCode,GC分代年龄,锁状态标志等
- 类型指针:对象指向它的类元数据的指针
- 实例数据:真正存放对象实例数据的地方
- 对齐填充:这部分不一定存在,也没有什么特别含义,仅仅是占位符。因为HotSpot要求对象起始地址都是8字节的整数倍,如果不是,就对齐
对象的访问定位
- 对象的访问定位:在JVM规范中只规定了reference类型是一个指向对象的引用,但没有规定整个引用具体如何去定位,访问堆中对象的具体位置
- 因此对象的访问方式取决于JVM的实现,目前主流的有:使用句柄或使用指针两种方式
- 使用句柄:Java堆中会划分出一块内存来做为句柄池,reference中存储句柄的地址,句柄中存储对象实例数据和类元数据的地址,如下图所示:
- 使用指针:Java堆中会存放访问类元数据的地址,reference存储的就直接是对象的地址,如下图所示:
- 使用句柄:Java堆中会划分出一块内存来做为句柄池,reference中存储句柄的地址,句柄中存储对象实例数据和类元数据的地址,如下图所示:
Java堆内存的分配参数
Trace跟踪参数
- 可以打印GC的简要信息:-Xlog:gc
- 打印GC详细信息:-Xlog:gc*
- 指定GC log的位置,以文件输出:-Xlog:gc:garbage-collection.log
- 每一次GC后,都打印堆信息:-Xlog:gc+heap=debug
GC展示的日志格式
- GC发生的时间,也就是JVM从启动以来经过的秒数
- 日志级别信息,和日志类型标记
- GC识别号
- GC类型和说明GC的原因
- 容量:GC前容量->GC后容量(该区域总容量)
- GC持续时间,单位秒。有的收集器会有更详细的描述,比如:user表示应用程序消耗的时间,sys表示系统内核消耗的时间,real表示操作从开始到结束的时间
Java堆的参数
- Xms:初始堆大小,默认物理内存的1/64
- Xmx:最大堆大小,默认物理内存的1/4
- Xmn:新生代大小,默认整个堆的3/8
- -XX:+HeapDumpOnOutOfMemoryError:OOM时导出堆到文件
- -XX:+HeapDumpPath:导出OOM的路径
- -XX:OnOutOfMemoryError:在OOM时,执行一个脚本
- -XX:NewRatio:老年代与新生代的比值,如果xms=xmx,且设置了xmn的情况下,该参数不用设置
- -XX:SurvivorRatio:Eden区和Survivor区的大小比值,设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor占整个新生的1/10
Java栈的参数
- -Xss:通常只有几百K,决定了函数调用的深度
元空间的参数
- -XX:MetaspaceSize:初始空间大小
- -XX:MaxMetaspaceSize:最大空间,默认是没有限制的
- -XX:MinMetaspaceFreeRatio:在GC之后,最小的Metaspace剩余空间容量的百分比
- -XX:MaxMetaspaceFreeRatio:在GC之后,最大的Metaspace剩余空间容量的百分比