java内存管理实例讲解

一.java虚拟机运行时内存分配图

二.栈 堆 方法区简介

1.栈

  1. 每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)

  2. JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)

  3. 栈属于线程私有,不能实现线程间的共享!

  4. 栈的存储特性是“先进后出,后进先出”

  5. 栈是由系统自动分配,速度快!栈是一个连续的内存空间!

2. 堆

  1. 堆用于存储创建好的对象和数组(数组也是对象)

  2. JVM只有一个堆,被所有线程共享

  3. 堆是一个不连续的内存空间,分配灵活,速度慢!

3.方法区

  1. JVM只有一个方法区,被所有线程共享!
  3. 用来存放程序中永远是不变或唯一的内容。(类信息【Class对象】、静态变量、字符串常量等)

三.实例讲解

1.代码

class Point {
    private double x;
    private double y;
    Point(double x1, double y1) {
        x = x1;
        y = y1;
    }
    public double getX() { return x; }
    public double getY() { return y; }
    public void setX(double i) { x = i; }
    public void setY(double i) { y = i; }
}
class Circle {
    private Point o;
    private double radius;
    Circle(Point p, double r) {
        o = p;
        radius = r;
    }
    public void setO(double x, double y) {
        o.setX(x);
        o.setY(y);
    }
    public Point getO() { return o; }
    public double getRadius() { return radius; }
    public void setRadius(double r) { radius = r; }
    public double area() { return 3.14 * radius * radius; }
    Circle increaseRadius() {
        radius++;
        return this; 
    }
}
public class TestCircle {
    public static void main(String args[]) {
        Circle c1 = new Circle(new Point(1.0, 2.0), 2.0);
        System.out.println("c1:" + c1.getO().getX());
        c1.setO(5, 6);
        System.out.println(c1.increaseRadius().increaseRadius().area());
    }
}

2. 构造函数 Circle c1 = new Circle(new Point(1.0,2.0), 2.0);

2.1 Circle c1

首先在栈内创建一个c1引用,指向地址不详

未命名文件

2.2 new Point(1.0,2.0)

在堆内创建一个Point对象 ,开始时Point内的变量X Y皆为0,

未命名文件 (1)

在栈内创建x1,y1,然后将其赋值给Point的x y,构造完成时内存分配如图

未命名文件 (1)

构造函数运行完成后,栈内x1,y1空间立刻释放

未命名文件 (1)

2.3 在堆内创建Circle对象 new Circle(new Point(1.0,2.0), 2.0)

同样,Circle对象刚创建时引用类型值为null,基础变量值为0,然后进入构造函数,在栈内创建一个指向刚才创建的Point对象的P和值为2.0的r,

未命名文件 (3)

然后用这两个元素初始化Circle内元素,使o指向Point,radius值变为2.0

未命名文件 (4)

构造函数完成后,栈内的r和p立刻释放

最后将c1指向Circle对象 Circle c1 = new Circle(new Point(1.0,2.0), 2.0);

未命名文件 (5)

3. c1.getO().getX()

首先调用geto获得c1所指向的Point,在栈中创建一个引用指向O引用

然后调用getX(),返回其X值,在栈内创建一临时对象用于存储X值

未命名文件 (6)

打印完成后,临时对象会被清理

4. c1.setO(5,6);

经过前面的学习,相信这个就很简单了,

在内存中创建临时变量x=5.0,y=6.0,然后通过赋值,改变c1内Point对象的值,SetO()结束后临时变量被清理.

5. c1.increaseRadius().increaseRadius().area() 关于this

首先c1调用increaseRadius()方法,返回一个Circle类的临时引用存储在栈内,指向c1对象,使radius变量+1.然后在通过这个临时引用再次调用increaseRadius()方法,使radius+1,再返回一个临时引用压栈,最后通过这个临时变量调用area()方法获取c1的增大半径后的面积.语句完成后两个栈内临时变量立刻被清理

未命名文件 (7)

四.static变量的内存分配

1.代码

class Cat {
    public static int num = 0;
    String name;

    Cat(String name) {
        this.name = name;
        ++num;
    }
}
public class Main {
    public static void main(String[] args) {
        Cat c1 = new Cat("mimi");
        Cat c2 = new Cat("miao");
        System.out.println(Cat.num);
    }
}

2.所有的字符串和static类型的静态成员变量都会预先加载于方法区内

所以上述代码执行时,内存分布如图,可以认为堆内c1,c2各有一引用指向方法区内对应元素

多态(动态绑定的内存分配)

1.代码

class Animal {
    public String name;
    Animal(String name) { this.name = name; }
    void enjoy() { System.out.println("叫声"); }
}
class Dog extends Animal {
    public String furName;    //毛皮颜色
    Dog(String name,String furName) { super(name); this.furName=furName;}
    @Override
    void enjoy() { System.out.println("汪汪汪"); }
}
class Cat extends Animal {
    Cat(String name) { super(name); }
    @Override
    void enjoy() { System.out.println("miao"); }
}
class lady {
    public String name;
    public Animal pet;
    lady(String name, Animal pet) {
        this.name = name;
        this.pet = pet;
    }
    void enjoy() { this.pet.enjoy(); }
    void replace_pet(Animal pet) { this.pet = pet; }
}
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("小黄","yellow");
        lady w1 = new lady("张不悔", dog);
        w1.pet.enjoy();
        Cat cat = new Cat("大白","white");
        lady w2=new lady("刘静",cat);
        w2.pet.enjoy();
    }
}

2.运行时,内存分布大致如图

在实际运行时,Lady内部的pet虽然是animal类型的引用,但是其指向的堆内存空间对象是一个完整的Dog对象,注意类内方法并不在堆内存储,类内方法存储在方法区内,堆中类对象只存储指向此方法的一个指针,因而打印出的enjoy结果是"汪汪",而不是"叫声".

但是因为pet类型是Animal,所以通过pet引用只能访问Animal类拥有的方法和变量,比如w1.pet.furname是错误的.因为pet引用并不知道他所引用的对象究竟是dog还是cat,只能判断出其至少是个Animal对象.

需要注意的是,栈内元素在使用完成后会立刻被清理,但是堆内创建的对象并不会立刻消失,而是在垃圾回收器启动回收后才会消失,而为了性能考虑,在内存充沛时,垃圾回收器并不会经常运行.所以可能某个元素已经空置了很久但是仍未被回收.
class Point {
    int x, y;

    Point(int _x, int _y) {
        x = _x;
        y = _y;
    }        //_x,_y在构造函数运行完成后会立刻消失
}

public class Main {
    public static void main(String[] args) {
        Point p1 = new Point(1, 2);
        Point p2 = new Point(2, 3);
        p1 = p2;         //p1之前所指向的Point对象已经成为空置垃圾,但是不会被立刻回收
    }
}
原文地址:https://www.cnblogs.com/INnoVationv2/p/12321197.html