JVM内存分配回收入门(一)

作者: 唠嗑008 | 来源:发表于2020-01-13 07:51 被阅读0次

    前沿

    菜鸟Android程序员入门jvm虚拟机,如有错误,欢迎批评指正。我们都知道Java的内存管理是交由jvm来管理的,这很方便,但是也带来了一些困扰,一旦出现内存泄漏和内存溢出方面的问题,如果不了解jvm虚拟机是如何分配回收内存的,那排除bug将会是非常困难的工作。

    为什么要学习jvm内存管理?
    我觉得起码学个入门级的吧,知道平时代码中那些对象,变量,方法在内存中是如何创建和回收的。我负责任的告诉你,真的不是为了装逼,而是为了解决实际问题。
    我的理由如下:
    1、解决常见的内存泄漏和oom;
    2、内存优化;
    3、在开发中写出更健壮的程序,减少一些内存bug或者说增加内存负担的问题;
    4、现阶段的面试大概率会问。

    关于运行时数据区

    在JVM加载class文件的时候,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。我们常说的内存管理(内存分配和回收)就是针对这段空间进行管理。

    JVM运行时数据区

    根据 JVM 规范,JVM 内存共分为方法区虚拟机栈本地方法栈程序计数器五个部分。其中方法区是被所有线程共享的一块内存区域,在虚拟机启动时创建,生命周期与虚拟机相同;而后面3个则是线程私有,生命周期与线程相同。

    注意:这是 JVM 规范,不同虚拟机,不同版本在实现上虽然有所不同,但总体上是按照这个规范来做的

    下面简单介绍一下这5个数据区域。

    • 方法区:是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量(final )、静态变量(static)、即时编译器编译后的代码等数据;
    • 堆(Heap):也是各个线程共享的内存区域,它的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存;它是jvm虚拟机管理的内存中最大的一块;
    • 虚拟机栈(VM Stack):线程私有,生命周期与线程相同,使用连续的内存空间来存储数据;栈描述的是Java 方法执行的内存模型;每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息;方法调用时会入栈,方法执行完成会出栈;
    • 本地方法栈:与虚拟机栈(VM Stack)的作用非常相似,区别在于Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务(是系统层面的);
    • 程序计数器:线程私有,占用内存小,生命周期与线程相同,大致为字节码行号指示器,记录的是正在执行的虚拟机字节码指令的地址。

    这里重点看一下方法区堆(Heap)虚拟机栈(VM Stack);而在平时的应用开发中做的内存优化又主要是堆(Heap),这下工作就变得没那么复杂了。

    下面通过一个例子来讲解内存分配:

    public class Person {
        public String name;
        public int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    public class Test {
        public static void main(String[] args) {
    
            int num = 1; //1
            Person person = new Person("张三", 20); //2
    
            change1(num); //3
            change2(person); //4
        }
    
        private static void change1(int n) {
            n = 11;
        }
    
        private static void change2(Person p) {
            p = new Person("李四", 21); //5
        }
    }
    

    内容很简单,在java的main()方法中定义了一个变量num,创建了一个对象Person,然后再调用change1(int n)change2(Person p)方法

    运行过程

    1

    首先这段java程序是从main()方法作为入口的,
    1、当运行到1时,会把局部变量num加载到栈内存;
    2、当运行到2时,jvm会先把Person .class加载到方法区,方法区里面存放的就是class的数据结构;然后在堆内存中开辟一个空间来存储Person的实例,且在堆内存的地址为0x001;接着把引用person放在栈中,这个引用指向的就是0x001
    3、当调用change1(int n)时,因为n是局部变量,所以会把n放到栈中,并且给n赋值,当方法执行完毕的时候,会立即释放n所占用的栈空间;
    4、 当调用change2(Person p)时,因为p是局部变量,所以会把p放到栈中,由于是引用类型的变量,pperson指向堆中的同一个对象;
    5、当执行到5时,会在堆内存中重新创建一个新的Person对象,然后p的指针地址不再指向之前的0x001,而是指向新的对象的地址0x002change2(Person p)方法执行完后会立即释放p的栈内存,但是在堆内存中的对象不会立即回收,而是等待gc自动回收。

    总结

    1、栈中存放的是局部变量(包括引用),如果是普通变量的话存放的是值;如果是引用的话,存放的是堆内存的指针;在使用时入栈,用完就立即出栈;
    2、堆内存存放的就是创建的对象/实例,堆是内存中比较大的一块区域,是垃圾回收器管理的主要区域,因此很多时候也被称做“GC 堆”;
    3、栈和堆的内存释放不是同步的。方法结束,栈中的局部变量立即销毁,但是堆中对象不一定马上销毁,而是等垃圾回收扫描时才可以被销毁,具体的算法后面章节再讲;
    4、创建一个对象时,类的成员变量会存储在堆内存,但是成员方法不会,你想想如果一个类创建了多个对象 ,每个方法都入栈,那多占内存啊?类的方法是该类的所有对象共享的,只有一套,在调用方法时,会通过堆内存中的实例找到方法区中相对应的方法。

    参考
    【Java千问】你了解Java内存结构么(Java7、8、9内存结构的区别)
    jvm学习一:类加载过程详解

    相关文章

      网友评论

        本文标题:JVM内存分配回收入门(一)

        本文链接:https://www.haomeiwen.com/subject/yggpactx.html