java进阶

可视化JVM理解运行时数据区(二)

2021-01-21  本文已影响0人  Jack_Ou

1. 工具以及配置

​ 要可视化代码在内存中的位置,以及虚拟机是如何划分运行时数据区的,我们需要使用到HSDB工具,该工具位于JDK\lib目录下。

​ 我们需要的工具如下:

​ 工具准备好了,那就行动吧。

HSDB启动界面.png

2.测试代码以及开始分析

public class ObjectInJvm {

    public final static String MAN_TYPE = "man";    //常量  存方法区
    public static String WOMAN_TYPE = "woman";  //静态变量  存方法区

    public static void main(String[] args) throws Exception {
        Student T1 = new Student(); // Student对象  存堆区   T1放在栈帧的局部变量表中
        T1.setName("DY");
        T1.setSexType(WOMAN_TYPE);
        T1.setAge(26);
        for (int i = 0; i < 15; i++) {
            System.gc();    // 经历15次GC,进入老年代
        }
        Student T2 = new Student(); // Student对象  存堆区   T2放在栈帧的局部变量表中
        T2.setName("JackOu");
        T2.setSexType(MAN_TYPE);
        T2.setAge(29);
        Thread.sleep(Integer.MAX_VALUE); //本地方法栈
    }
}

class Student {

    String name;
    String sexType;
    int age;

    ......
    //get,set方法
}
2.1 介绍工具
运行demo.png demo运行进程号查看.png demo进程的线程信息.png main线程栈帧信息.png 栈区内存.png

3. 程序运行时内存变化分析

3.1 JVM运行java代码处理过程
3.2 运行时数据区分析
栈区内存地址分析.png

堆区地址分配.png

注意:准备放大招了,前方大招预警!!!!

​ 因此我们把以上从HSDB中看到的内存地址和各个区放在同一张图中,就可以很清晰得看清楚代码在运行时,对象在各个区中的运动轨迹。如下图

运行时数据区结合HSDB分析.png
//开始认真分析!!!
//=============第1步=================
//new Student()在堆区创建一个对象,并且在栈区的局部变量表中引用这个新对象的地址
Student T1 = new Student();   
// 在堆区内存空间中找到对应位置并赋值
T1.setName("DY");  
T1.setSexType(WOMAN_TYPE);
T1.setAge(26);

//=============第2步=================
// 因为T1是强引用,系统无法回收T1所指的对象,因此T1被挪到了老年代,从地址也可以看到
for (int i = 0; i < 15; i++) {
    System.gc();
}
//=============第3步=================
//new Student()在堆区创建一个对象,并且在栈区的局部变量表中引用这个新对象的地址
//由于是才创建的,从地址可以看到T2所指向的对象被分配在Eden区
Student T2 = new Student();
T2.setName("JackOu");
T2.setSexType(MAN_TYPE);
T2.setAge(29);

4. 总结对JVM堆和栈的认识

​ 谈到内存空间大小,在开发中经常遇到OOM,要么是内存泄漏导致堆溢出,要么是递归调用没有选好结束条件导致栈溢出等等,那么下面我们再来看看OOM包括些什么。

4.1 堆溢出

堆内存溢出:创建对象时申请内存空间,超出最大堆内存空间。

解决方案:如果不是内存泄漏,就是说内存中的对象却是都是必须存活的,那么久应该检查JVM的堆参数设置,与机器的内存对比,看是否还有可以调整的空间,再从代码上检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行时的内存消耗。Android开发中常见的是因为内存泄漏和Bitmap管理不当造成堆溢出。

4.2 栈溢出

栈溢出:因为每个方法的执行都需要打包成栈帧,一般的方法调用是很难出现的,如果出现了可能会是无限递归。另一种可能是大量创建线程,JVM不断申请栈内存,导致机器没有足够的内存报OOM,因此一般在开发中,最好用线程池来管理线程。

4.3 方法区溢出

5. 虚拟机优化技术

5.1 对内存的优化

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

JVM栈空间优化.png
public class JvmStackOpt {

    public int work(int x) throws Exception{
        int z =(x+5)*10;
        Thread.sleep(Integer.MAX_VALUE);
        return  z;
    }
    public static void main(String[] args)throws Exception {
        JvmStackOpt jvmStack = new JvmStackOpt();
        jvmStack.work(10);  // 变量10的内存空间被共享了
    }
}
JVM栈空间优化取证.png
5.2 编译优化技术——方法内联
public static void main(String[] args) {
   // boolean i1 = max(1,2);
    //调用max方法:  虚拟机栈 --入栈(max 栈帧)
    boolean i1 = 1>2;
}
public static boolean max(int a,int b){//方法的执行入栈帧。
    return a>b;
}
上一篇 下一篇

猜你喜欢

热点阅读