jvm深度学习(6):实例代码运行JVM内存处理全流程、堆的垃圾回收、栈优化技术

 

实例代码运行JVM内存处理全流程

提问:当一个实例在运行时我们的JVM究竟发生了什么?

先写个代码,然后运行

public class JVMObject {
    public final static String MAN_TYPE = "man"; // 常量
    public static String WOMAN_TYPE = "woman";  // 静态变量
    public static void  main(String[] args)throws Exception {
        Teacher t1 = new Teacher();
       
    }
}

代码运行时,JVM共做了5件事

 

1、JVM申请内存

         就是向我们的操作系统申请内存,根据我们设置好的参数 (-Xms30m -Xmx30m  -XX:MaxMetaspaceSize=30m )获得元空间、虚拟机栈等需要的内存。

2、初始化运行时数据区

由于方法区和堆属于线程共享所以会先初始化出来

3、类加载

该过程会将类信息、一些静态常量、静态变量等信息添加到方法区

4、执行方法

在我们的代码里面执行到main方法,虚拟机栈属于线程私有所以方法执行时初始化内存,然后压入栈帧,

5、创建对象

执行Teacher t1 = new Teacher();

首先现在堆中创建一个对象

然后在栈中添加一个引用指向堆中的Teacher

 堆的垃圾回收(只简单概述,后面会细谈)

对上面代码进行加工

public class JVMObject {
    public final static String MAN_TYPE = "man"; // 常量
    public static String WOMAN_TYPE = "woman";  // 静态变量
    public static void  main(String[] args)throws Exception {
        Teacher t1 = new Teacher();
        t1.setName("Mark");
        tT1.setSexType(MAN_TYPE);
        t1.setAge(36);
        for(int i =0 ;i<15 ;i++){
            System.gc();//主动触发GC 垃圾回收 15次--- t1存活
        }
        Teacher t2 = new Teacher();
        t2.setName("King");
        t2.setSexType(MAN_TYPE);
        t2.setAge(18);
        Thread.sleep(Integer.MAX_VALUE);//线程休眠
    }
}

注意:这是演示代码,千万不要代码中使用 System.gc();java有很好的自动垃圾回收机制,不需要我们手动去触发。而且垃圾回收器执行时会占用很多的资源,会影响到项目正常的运行

 

 堆分为4个区域Eden、From、To、Tenured

        一般情况下大多数新创建的对象都会在Eden中,Tenured是存放比较难回收的对象,From和To的存在是为了充分利用空间和高效垃圾回收设计的,不细说后面会细细描述。

首先执行上面代码,之后堆中对象的分布是这样的

如果执行这段代码

public class JVMObject {
    public final static String MAN_TYPE = "man"; // 常量
    public static String WOMAN_TYPE = "woman";  // 静态变量
    public static void  main(String[] args)throws Exception {
        Teacher t1 = new Teacher();
        t1.setName("Mark");
        tT1.setSexType(MAN_TYPE);
        t1.setAge(36);
        Teacher t2 = new Teacher();
        t2.setName("King");
        t2.setSexType(MAN_TYPE);
        t2.setAge(18);
        Thread.sleep(Integer.MAX_VALUE);//线程休眠
    }
}

之后堆中对象的分布是这样的

我们发现两段代码的不同之处只是for循环15次的垃圾回收

        也就是说,在我们创建对象最开始是在Eden区,经历了15次垃圾回收失败之后(t1属于强引用,回收不了)存入位置换到了Tenured区。

        其实在进入到Ternured区之前,t1一直在From和To区通过复制清除算法来回切换。直到第15次回收失败才到Ternured区。

        至于为什么是15次,原因是这样的:每个对象有个对象头,对象头中有一块区域存放的是age,这个区域大小是4位,2的四次方=16,在刚进入Eden区时是1,所以只能进行15次。

 

栈优化技术——栈帧之间数据的共享

        在一般的模型中,两个不同的栈帧的内存区域是独立的,但是大部分的 JVM 在实现中会进行一些优化,使得两个栈帧出现一部分重叠。(主要体现在方 法中有参数传递的情况),让下面栈帧的操作数栈和上面栈帧的部分局部变量重叠在一起,这样做不但节约了一部分空间,更加重要的是在进行方法调用时就可以直接公用一部分数据,无需进行额外的参数复制传递了。

代码示例:

public class JVMStack {

    public int work(int x) throws Exception{
        int z =(x+5)*10;//局部变量表有, 32位
        Thread.sleep(Integer.MAX_VALUE);
        return  z;
    }
    public static void main(String[] args)throws Exception {
        JVMStack jvmStack = new JVMStack();
        jvmStack.work(10);//10  放入main栈帧  10 ->操作数栈
    }
}

        通过代码我们看到 jvmStack.work(10)方法传入常量10,此时这个10在main栈帧的操作数栈上。而随后又将压入新的栈帧用来执行work方法,而此时work栈帧第一件事,肯定是将main方法传入过来的常量10,通过work的操作数栈存入到局部变量表中。

        事实上,在JVM中work局部变量表存放的常量10,和main方法操作数栈的常量10共用一块物理内存。这就意味着,jvm直接将main栈帧操作栈上的常量10,用作work栈帧局部变量表存放常量10,共用物理内存,简化操作,极大节省了内存和提高了效率。

对于上面的代码执行使用 JHSDB 工具查看栈空间下面红线标红的重叠部分就此存放常量10的物理内存。

 

 

声明:本文为学习享学课堂三期JVM章节课后所做的学习总结,文中借鉴King老师上课笔记和课堂PPT。

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值