运行时数据区
JVM 由三部分组成:类加载系统、运行时数据区、执行引擎
下边讲一下运行时数据区中的构成
根据线程的使用情况分为两类:
- 线程独享(此区域不需要垃圾回收)
- 虚拟机栈、本地方法栈、程序计数器
- 线程共享(数据存储区域,此区域需要垃圾回收)
- 存储类的静态数据和对象数据
- 堆和方法区
堆
Java 堆在 JVM 启动时创建内存区域去实现对象、数组与运行时常量的内存分配,它是虚拟机管理最大的,也是垃圾回收的主要内存区域
在 JDK1.8 中,堆由两部分组成:新生代和老年代
而在 JDK1.9 中,取消了新生代和老年代的物理划分,将堆划分为若干个区域 Region,如下图:
可以通过代码查看堆空间的大小:
public class HeapSpaceInitial {
public static void main(String[] args) {
/**
使用Runtime.getRuntime()获取当前 (运行时数据区) , 是单例的。
*/
// 返回Java虚拟机中的堆内存总量
long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
// 返回Java虚拟机试图使用的最大堆内存量
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
System.out.println("-Xms: " + initialMemory + "M");
System.out.println("-Xmx: " + maxMemory + "M");
System.out.println("系统初始内存大小为: " + initialMemory * 64.0 / 1024 + "G");
System.out.println("系统最大内存大小为: " + maxMemory * 4.0 / 1024 + "G");
/**
输出:
-Xms: 243M
-Xmx: 3609M
系统初始内存大小为: 15.1875G
系统最大内存大小为: 14.09765625G
*/
}
}
通过命令行查看堆中的参数:
jps # 查看运行的进程
jstat -gc 进程id # 查看该进程的堆中参数
通过 VM options 查看垃圾回收时的信息:
-XX:+PrintGCDetails
Java 中新创建的对象如何分配空间呢?
- new 的对象先放 Eden 区(如果是大对象,直接放入老年代)
- 当 Eden 区满了之后,程序还需要创建对象,则垃圾回收器会对 Eden 区进行垃圾回收
- 在垃圾回收的时候,会将 Eden 区的幸存对象转移到 Survivor From 区
- 如果再次触发垃圾回收,此时将 Eden 区的幸存对象转移到 Survivor To 区中,并且将 Survivor From 区中的幸存对象也转移到 Survivor To 区
- 如果再次出发垃圾回收,此时将 Eden 区和 Survivor To 区中的幸存对象转移到 Survivor From 区中
- 当对象的生存年龄达到 15 时,会被放入老年代
在幸存对象每次转移的时候,对会将对象的生存年龄 + 1,达到 15 时会放入老年代中
Java 对象只会分配在堆中吗?
不是的,如果经过 逃逸分析
后发现,一个对象并没有逃逸出方法的话,就可能被优化为在栈上分配
,这是常见的堆外存储技术。
逃逸分析就是分析对象动态作用域:
- 对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸
- 对象在方法中被定义后,对象被外部方法所引用,则认为发生逃逸