Java内存分配

转载自:http://blog.csdn.net/l18320786461/article/details/78614334#comments

感觉这篇写的不错,所以转载下;

作为一名程序员对于我们编写的代码块是如何存储、放在哪里、分配、回收,还是要有一些了解的。本人做Android开发,面试的时候面试官大部分都会问关于内存这块的相关知识点(什么情况导致内存泄露、如何优化),有点同学会说:那还不简单,上网找一下相关的面试题就OK了。但是我觉得还是不够的。今天给大家分享一下Java内存分配的一些看法。

首先我们需要知道Java内存分为那几个部分:

寄存器(Registers):速度最快的存储场所,因为寄存器位于处理器内部我们在程序中无法控制

栈(Stack):存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中;也可以保存加载的方法

堆(Heap):堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器(GC)来管理。

静态域(static field):  静态存储区域就是指在固定的位置存放应用程序运行时一直存在的数据,Java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量

常量池(constant pool):虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和floating point常量)和对其他类型,字段和方法的符号引用。

大致如下图所示:


1、栈和堆:

每当我们创建一个新的对象时,Java都会给该对象分配一个地址;对象本身存储在堆中,而该对象的引用存储在栈中。

来个简单的例子:

我们创建一个类:

public class Student {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

在主方法中创建该类的对象:

public class StackAndHeap {

    public static void main(String[] args){

        Student student = new Student();
        Student student1 = new Student();

        System.out.println(student==student1);
    }

}
打印结果: false

画图分析:


其实我们每次调用student或者student1时都会先从栈中找得到对应的内存地址,然后根据内存地址在堆中找到对应的对象,再对对象进行操作。对象不再被引用时,堆指向栈的那条线也就断开了,栈中的对象引用也会退出栈。

2、静态变量

静态变量是在.class文件被加载到jvm的时候就被加载到内存中的,它是这个类的共享数据,随着jvm消失其所占的内存才会释放。

比如我们常说的线程同步问题:卖票。假设我们不用Runnable(数据共享)实现线程,而是创建Thread类的话,那么我们的票的数量这个属性就必须要设为静态的,防止抢票意外的话同步锁也要设置为静态的。

在我们一般给方法设为静态时,较多的用在工具类上。

3、基本类型和常量池

基本类型分为:整数(byte【1字节】、short【2字节】、int【4字节】、long【8字节】)、小数(double【8字节】、float【4字节】)、字符、布尔。

Java为了提高性能提供了和String类一样的对象池机制,当然Java的八种基本类型的包装类(Packaging Type)也有对象池机制

我们先来看个例子:

public static void main(String[] args){
    int q=10;
    int w = 10;
    int e = 0;
    Integer a = 10;
    Integer s = 10;
    Integer d = 0;
    Integer z = new Integer(10);
    System.out.println("q=w? "+(q==w));
    System.out.println("q=a? "+(q==a));
    System.out.println("a=s? "+(a==s));
    System.out.println("q=w+e? "+(q==w+e));
    System.out.println("a=s+d? "+(a==s+d));
    System.out.println("a=z? "+(a==z));
    System.out.println("a=z+e? "+(a==z+e));
}

输出结构:

q=w? true
q=a? true
a=s? true
q=w+e? true
a=s+d? true
a=z? false
a=z+e? true

结果分析:

q和w普通类型变量,他们存储在栈中,而栈有一个很重要的特性:栈中的数据可以共享。当q=10时,会在栈中寻找是否有10,有的话就直接把10指向q,没有的话就生成一个内存里面的值为10,再把10指向q;当w=10时也会做同样的操作,所以他们2个的地址相等。

q和a对比:q==a对比时Integer会自动拆箱,把a转为int和q对比(intvalue());a==q对比时会把装箱成Integer(valueof())。

a=z+e,这里也是对z+e进行了拆箱处理,然后和a进行比较。

注意:new Integer()底层的常量池大小为-128-127,超出这个范围的话Integer i = 400和 Integer j = 400,这里的i和j不相等,因为超出-128-127的话回去调用new Integer()产生出来的对象地址不相等。

4、Java方法传参

public static void main(String[] args){

    Integer a = new Integer(0);
    test(a);
    System.out.println("a="+a);
}


private static void test(Integer aa){
    aa=1;
    System.out.println("aa="+aa);
}

输出结构:

aa=1
a=0

本来想打印出参数的地址,谁知Java不支持(操蛋),Java中方法传参传的是这个对象的值;而C/C++中可以传地址,这里给大家演示一下。

#include "stdafx.h"
#include<iostream>
#include<cmath>
#include"student.h"
#include "ConsoleApplication1.h"


void fun(MyClass& student3) {
    student3.print();
    printf("student3==%x\n", &student3);
    cout << "fun has used" << endl;
}

int main()
{

    using namespace std;

    
    MyClass student;
    student.print();
    printf("&student=%x\n", &student);
    MyClass student2 = student;
    printf("student2==%x\n", &student2);
    student2.print();
    fun(student2);

    return 0;
}

控制台输出结果:

MyClass has construct
print has construct
i has change
&student=6ffb38
construct copy
student2==6ffb2c
print has construct
i has change
print has construct
i has change
student3==6ffb2c

我们可以看到student2student3的地址相等。

本人初次写博客讲的比较浅显,有不足和错误的地方欢迎大佬指正。

猜你喜欢

转载自blog.csdn.net/wqbs369/article/details/79648887