Java内存分析(1)——内存分区和分配过程

我们已经习惯于写出类似Object obj=new Object();型的语句,然而背后究竟发生了什么?从JVM内存结构分析更有助于加深理解记忆。下面试着举例说明:

JVM内存分区:

这里写图片描述

如上图所示,JVM主要分为以上几块:程序计数器,本地方法栈,虚拟机栈,堆和方法区。稍微粗糙一些得分法是JVM分为栈和堆,栈包括虚拟机栈,本地方法栈,程序计数器,堆分为堆和方法区。需要说明的是这里所说的Java内存和硬件上的内存并不能完全对应,Java内存还可能包含CPU的寄存器,高速缓存等。

程序计数器

程序计数器是用于存储每个线程下一步将执行的JVM指令,如该方法为native的,则程序计数器中不存储任何信息。

本地方法栈

众所周知,Java的底层是C实现的,所有调用的C代码都被称为本地方法(native method)。JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。

虚拟机栈

JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。

以上三块是伴随着线程同生共死的,生命周期由编译器控制。

堆 Java Heap

它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Java Heap是线程共享的。Heap中的对象的内存需要等待GC进行回收。

方法区

方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息。方法区域也是全局共享的,在一定的条件下它也会被GC。

其中,方法区中有一个运行常量池:存放的为类中的固定的常量信息、方法和Field的引用信息等。

内存分配过程

了解了JVM的内存分区,接下来就以实例分析到底变量都存放在哪里。由于下面的例子和本地方法栈,程序计数器关系不大,所以暂时略去。

一段司空见惯的代码:

public class student{
    String name;
    Integer age;
    String score    
    public void study(){
        system.out.println("I am studing");
    }   
    public void sayHello(){
        system.out.println("Hello");
    }
}

public class computer(){    
    String brand;
    String speed;
}

public class test(){
    public static void main(String args[]){
        Student stu1 =new Student();
        stu1.name="wang";
        stu1.age=24;

        Student stu2=new Student();
        stu2.name="li";
        stu2.age=23;

        stu1.computer.brand="Lenovo";
        stu1.speed="2.5GHz"
    }
}

内存存储详情图:

这里写图片描述

由图中所示:
栈存储的是局部变量,如上述代码中的stu1,stu2。
堆的heap区存储的是new创建出的变量。
堆的方法区存储的是类的方法(动态,静态),变量(动态,静态),常量。

我们知道:Java中,除了基本数据类型,都是引用类型,也就是说这些数据类型存的都是引用地址,直至一个接一个的引用,最终指向到基本类型,基本类型存储的才是值的本身。

这样就可以理解图中的所有箭头,一条箭头代表一个引用,箭尾是引用的地址,箭尖是被引用的变量。

由此,我们来剖析代码在内存中是如何实现的:
1,首先JRE加载了编译好的class文件,将相关类的方法,变量,常量都放到了类区。然后开始执行语句。

2,执行第一句语句Student stu1=new Student();
按照Java由右向左逐步执行的机制,首先是运行new Student()在Java Heap中开辟出一个可以存储Student类所有属性和方法的引用的连续空间。方法都指向了方法区的对应方法,而属性在未赋值的情况下都是null或0。
之后是执行Student stu1在栈空间内分配一个空间存储一个存储Student类型变量引用的空间。
最后运行等号,等号就是将变量区中变量的地址赋值给栈中的变量,即建立了引用关系。

3, 执行第二句:stu1.name="wang";“wang”是一个已经存在于方法区的常量,stu1.name是变量区中的一个空变量,等号将两者连接,将“wang”的地址赋给了stu1.name。
以此类推,以下都如图所示。被赋值的变量存储了相应的地址,未赋值的保持为空。

4,值得注意的是,Java Heap的变量也可以相互引用,例如stu1.computer就是从一个变量引用到另一个变量。

静态变量与方法与与一般变量和方法所不同的是,在用new关键词创建类对象时,这个类里面的static变量和方法不会被初始化,栈中的对象直接对它们进行引用。因此,静态方法无需实例化即可使用,静态变量被类的所有实例所共享。

顺带提一句垃圾回收机制(garbage collection),Java采用的是自动的垃圾回收机制,当虚拟机,发现某一个变量在之后不会被使用到,例如图中stu1已经用完了,就会把stu1引用的一整块在变量区的内容释放掉。

垃圾回收是一个后台线程,在不知不觉中帮我们管理着内存,好处是解放了程序员,编写程序少操了一份心,坏处是相比C++这种需要主动释放内存的语言少了一点灵活性。Java可以使用System.gc()来提醒虚拟机可以考虑进行一下垃圾回收,但是系统可能不一定听你的,你可以建议,但做不做由它,Java这种脾气在很多地方都有体现。

总结一下,
Java内存管理分为两大块,栈和堆,堆又分为变量区和类区;
栈引用变量区,变量区引用类区,变量区还相互引用,类区内部也相互引用;
Java的有自动的垃圾回收机制,可以将不再被使用的变量释放。
核心:一定要深入理解Java的引用机制。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
操作系统内存动态分区分配算法主要有三种:首次适应算法(First Fit)、最佳适应算法(Best Fit)和最差适应算法(Worst Fit)。下面是它们的Java实现。 首次适应算法(First Fit): ```java public class FirstFit { private int[] memory; // 内存空间 private boolean[] status; // 内存状态,true表示已被占用,false表示未被占用 public FirstFit(int size) { memory = new int[size]; status = new boolean[size]; } /** * 分配内存 * * @param size 请求的内存大小 * @return 分配的起始地址,若分配失败则返回-1 */ public int allocate(int size) { for (int i = 0; i < memory.length; i++) { if (!status[i]) { // 找到一个未被占用的内存块 int j = i; int count = 0; while (j < memory.length && !status[j] && count < size) { // 查找连续的未被占用的内存块 count++; j++; } if (count == size) { // 找到了满足要求的内存块 for (int k = i; k < j; k++) { status[k] = true; // 将内存状态设置为已被占用 } return i; // 返回分配的起始地址 } else { i = j - 1; // 继续查找下一个未被占用的内存块 } } } return -1; // 分配失败 } /** * 释放内存 * * @param start 起始地址 * @param size 释放的内存大小 */ public void deallocate(int start, int size) { for (int i = start; i < start + size; i++) { status[i] = false; // 将内存状态设置为未被占用 } } } ``` 最佳适应算法(Best Fit): ```java public class BestFit { private int[] memory; // 内存空间 private boolean[] status; // 内存状态,true表示已被占用,false表示未被占用 public BestFit(int size) { memory = new int[size]; status = new boolean[size]; } /** * 分配内存 * * @param size 请求的内存大小 * @return 分配的起始地址,若分配失败则返回-1 */ public int allocate(int size) { int min = Integer.MAX_VALUE; // 初始化最小空闲块大小 int index = -1; // 初始化最小空闲块的下标 for (int i = 0; i < memory.length; i++) { if (!status[i]) { // 找到一个未被占用的内存块 int j = i; int count = 0; while (j < memory.length && !status[j] && count < size) { // 查找连续的未被占用的内存块 count++; j++; } if (count >= size && count < min) { // 找到了满足要求的内存块 min = count; index = i; } i = j - 1; // 继续查找下一个未被占用的内存块 } } if (index != -1) { // 分配内存 for (int i = index; i < index + size; i++) { status[i] = true; // 将内存状态设置为已被占用 } return index; // 返回分配的起始地址 } else { return -1; // 分配失败 } } /** * 释放内存 * * @param start 起始地址 * @param size 释放的内存大小 */ public void deallocate(int start, int size) { for (int i = start; i < start + size; i++) { status[i] = false; // 将内存状态设置为未被占用 } } } ``` 最差适应算法(Worst Fit): ```java public class WorstFit { private int[] memory; // 内存空间 private boolean[] status; // 内存状态,true表示已被占用,false表示未被占用 public WorstFit(int size) { memory = new int[size]; status = new boolean[size]; } /** * 分配内存 * * @param size 请求的内存大小 * @return 分配的起始地址,若分配失败则返回-1 */ public int allocate(int size) { int max = Integer.MIN_VALUE; // 初始化最大空闲块大小 int index = -1; // 初始化最大空闲块的下标 for (int i = 0; i < memory.length; i++) { if (!status[i]) { // 找到一个未被占用的内存块 int j = i; int count = 0; while (j < memory.length && !status[j] && count < size) { // 查找连续的未被占用的内存块 count++; j++; } if (count >= size && count > max) { // 找到了满足要求的内存块 max = count; index = i; } i = j - 1; // 继续查找下一个未被占用的内存块 } } if (index != -1) { // 分配内存 for (int i = index; i < index + size; i++) { status[i] = true; // 将内存状态设置为已被占用 } return index; // 返回分配的起始地址 } else { return -1; // 分配失败 } } /** * 释放内存 * * @param start 起始地址 * @param size 释放的内存大小 */ public void deallocate(int start, int size) { for (int i = start; i < start + size; i++) { status[i] = false; // 将内存状态设置为未被占用 } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值