程序计数器(寄存器)
程序计数器 是线程私有的
记录当前线程所执行的字节码行号(执行的字节码的偏移地址),用于获取下一条执行的字节码。
为什么需要程序计数器 我想原因之一是
因为多线程运行时,每个线程切换后需要知道上一次所运行的状态、位置。所以由此也可以看出程序计数器是每个线程私有的。
虚拟机栈
Java虚拟机栈 描述的是 Java方法执行的内存模型
用于存储栈帧
每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈(压栈)到出栈(弹栈)的过程。
同样属于线程私有
-
局部变量表
局部变量表用于存放方法参数和方法内部定义的局部变量 -
操作数栈
-
动态链接
-
方法出口
如果栈太深会造成栈内存溢出错误 StackOverflowError 比如 没有出口的递归函数
可以通过-Xss 设置堆栈大小
虚拟机栈还有可能造成OutOfMemoryError 造成原因可能当前线程的内存耗尽 并且无法申请内存
本地方法栈
Navtive 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用到 C/C++ 方法。当线程调用 Java 方法时,虚拟机会创建一个栈帧并压入 Java 虚拟机栈。然而当它调用的是 native 方法时,虚拟机会保持 Java 虚拟机栈不变,也不会向 Java 虚拟机栈中压入新的栈帧,虚拟机只是简单地动态连接并直接调用指定的 native 方法
Java堆
Java堆是垃圾收集器管理的主要区域 堆内存分为新生代和老年代 新生代
又被划分为三个区域 Eden from Survivor To Survivor
新生代占1/3堆空间 老年代占2/3堆空间
如果Eden未满 对象会优先在Eden中创建
如果Eden的容量已满 则触发Minor GC(会回收Eden和Survior中的一块) 将还
活跃的对象复制到另一块Survivor中
如果Survior中的内存 也满了或者Survior放不下则直接放入老年代
大对象直接进入老年代
这是因为
系统中出现大量大对象是很影响性能的,这样会导致还有不少空间时就提前触发垃圾回收来放置这些对象。
触发GC时的代码
[Full GC (Ergonomics) [PSYoungGen: 752K->0K(9216K)] [ParOldGen: 6152K->6775K(10240K)] 6904K->6775K(19456K), [Metaspace: 2574K->2574K(1056768K)], 0.0068214 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
752K的缓冲删除 成功 6152K 移动后 变为6775K ok
年轻代移动到老年代6904->6775K ok
public class EdenTest {
private static final int _1MB = 1024*1024;
/**
* 虚拟机参数设置:-XX:+UseParallelGC -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
* @param args
*/
public static void main(String[] args) {
byte[] a = new byte[2*_1MB];
byte[] b = new byte[2*_1MB];
byte[] c = new byte[2*_1MB];
byte[] d = new byte[3*_1MB];
}
}
java -XX:+UseParallelGC -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 EdenNOTGCTest
- -Xms20M表示设置初始堆大小,
- -Xmx20M 表示最大堆大小20M
- -Xmn 新生代大小 实际可用空间为Eden+1个Survivor即90%
- -Xss 每个线程的堆栈大小 一般来说如果栈不是很深的话 1M绝对够用
[PSYoungGen: 752K->0K(9216K)]
9216K表示的是Eden+一块Surivor的空间
回收机制(部分)
PSYoungGen的意思是使用Parallel Scavenge 收集器
当再一次触发Minor GC后,S0和Eden 中存活的对象被移动到S1中(From s0To s1),S0即被清空。在同一时刻, 只有Eden和一个Survivor Space同时被操作
虚拟机给每个对象定义了一个年龄计数器(Age),如果对象在新生代Eden创建,并经历一次 Minor GC 后仍然存活,并且能够被 Survivor 容纳的话,虚拟机会将该对象移动到 Survivor 区域,并将对象的年龄Age+1。
新生代对象每熬过一次 Minor GC,年龄就增加1,当它的年龄增加到一定阈值时(默认是15岁),就会被晋升到老年代中。
可以通过 -XX:MaxTenuringThreshold
来设置阈值
当未发生GC时存放的大小 7456-6144
发生GC时存放的大小
JVM 元空间
JVM内存划分分为堆区和非堆区
非堆区 主要包括用于编译和保存本地代码的
JVM用于描述应用程序中用到的类和方法的元数据也存储在持久代中
元数据在Java中的意思是注解
对象的内存布局
在HotSpot虚拟机中 对象在内存中存储的布局可以分为
对象头(对象自身的运行时数据比如hash码)锁状态标志 线程持有的锁 偏向线程ID 实例数据(对象的有效信息) 对齐填充
当创建对象后 大致分为
- 类的加载检查
- 为对象分配内存
- 内存空间初始化
- 对象的设置 对象头 对齐填充等
- init
参考链接
https://www.jianshu.com/p/ac162726d7de