JVM浅析

程序计数器(寄存器)

程序计数器 是线程私有的
记录当前线程所执行的字节码行号(执行的字节码的偏移地址),用于获取下一条执行的字节码。
为什么需要程序计数器 我想原因之一是
因为多线程运行时,每个线程切换后需要知道上一次所运行的状态、位置。所以由此也可以看出程序计数器是每个线程私有的。

虚拟机栈

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 实例数据(对象的有效信息) 对齐填充

当创建对象后 大致分为

  1. 类的加载检查
  2. 为对象分配内存
  3. 内存空间初始化
  4. 对象的设置 对象头 对齐填充等
  5. init

参考链接

https://www.jianshu.com/p/ac162726d7de

发布了17 篇原创文章 · 获赞 3 · 访问量 358

猜你喜欢

转载自blog.csdn.net/qq_40184765/article/details/104819269
今日推荐