浅谈Java虚拟机JVM的内存模型以及Java的参数传递

一、Java的内存模型

关于Java运行时的内存模型,布局,大部分人了解熟悉的就是堆和栈(这也是我们最关心的俩个区域),然而实际上,JVM的内存模型其实远远不止这两块。实际上,JVM讲内存划分为了5大模块:1、方法区。2、堆。3、JVM栈。4、本地方法栈。5、程序计数器(这5大区域里面,线程私有的是3,4,5这三个模块(即线程自己拥有的空间,其他线程不能访问到的),而线程共享(就是所有线程都能够访问的数据)的则是1和2)。下面笔者就会详细讲解每个区域到底存放那些数据,又有那些特性。

1、方法区

这块区域存放的数据就是已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等。GC收集器(垃圾收集器)很少光顾这里(关于GC收集器,笔者会在下一篇文章中提及)。

2、堆

堆,可以说是非常重要的一个区域,它存放了几乎所有程序中new出来的对象实例(new出来的对象,都在堆中分配内存,当然,在最新的jdk版本中,这已经不是绝对正确的了)。因为几乎所有的对象实例都在这里分配,所以GC收集器回收的内存大部分都是从这里回收。

可以说,我们new出来的对象的各种数据都是存放在堆里面的,比如创建的Person类里面的name属性的值,你都可以在这里找到。

3、JVM栈

JVM栈,描述的是Java方法执行时的内存模型。每一个方法在执行的同时,都会创建一个栈帧,用于存储局部变量表等各种数据,方法中所有你用到的变量都会在这里找到(基本数据类型,如int,long,double这种,就是保存的变量的值,而像对象,String这种引用类型的变量,它保存的并不是值,而是一个地址,这个地址就指向真实的数据)。

在这里,可能有读者会想到,上面说了,所有new出来的对象都在堆中分配内存,而栈又是用于存储方法的局部变量等数据的,那如果在方法里面,new了一个局部变量出来,这个变量到底是在堆中还是栈中?

就如上面括号中的内容所说,如果在方法里面new了一个局部变量出来,这个变量其实还是在堆中分配的内存,但是,会在栈中存放这个变量的地址,该地址就指向堆中这个变量分配的内存地址。讲到这里,笔者就想说一下关于java参数传递中经常会出错的情况,看如下代码:

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TestUnitl
{
    //@Autowired
    //private IUserLoginServiceImpl userLoginService;
    //
    //@Test
    //public void testForConfig()
    //{
    //    UserAccountEntity test = userLoginService.verifyLoginUser("123456","123456");
    //    System.out.println("搜索的结果:" + test);
    //}

    public static void main(String[] args)
    {
        TestUnitl testUnitl = new TestUnitl();

        String originData = "我是原始数据";

        System.out.println("原始数据originData的值是:" + originData);

        testUnitl.changeValue(originData);

        System.out.println("原始数据originData的值是:" + originData);
    }

    public void changeValue(String data)
    {
        System.out.println("传递给形参的值是:" + data);
        data = "我改变了";
        System.out.println("改变之后的值:" + data);
    }
}

在看答案之前,读者可以先自己阅读代码,想一下你得出的控制台输出是哪些数据?

下面是这段代码运行之后的结果:

看到答案,可能会有读者感到疑惑,我不是已经把originData传到方法里面,对他进行重新赋值,修改了值了吗,为什么最后输出的结果还是原来的值?

这里,就要考虑上面笔者所说的堆和栈,以及栈中保存的数据了。其实,每个方法的形参,也都相当于是一个局部变量,它也会在栈中分配一个内存用来保存它的值。当你将方法外部的实参传递给形参的时候,对于基本数据类型,就相当于是给形参这个变量赋值了,将他内存的数据修改为你实参一样的数据了,然后在方法内部对形参进行操作时,其实操作的都是这个形参的内存,并没有操作到实参的内存,如下图所示:

而对于引用类型,比如对象,或者String这种类型,实参和形参其实存放的是对象的内存地址(对象在堆中分配,所以这个地址指向堆中的某个位置),并没有保存实参的数据的值。这个内存地址指向了对象的首地址。所以,当在方法体内部用“=”给形参赋值时,其实真正的操作是,修改了形参存放的内存地址,所以形参就指向了另外一个对象,但是实参,它还是指向的原来的对象,所以最后你打印出来的实参变量,他并没有发生改变。(读者可以参考一下上面的图,就是把数值换成了一个内存地址,比如5,改成0x12345678这总内存地址,然后0x12345678这个内存地址的位置是在堆里面,这个地址就是一个对象数据的首地址,存放了对象的数据,这个地址之后就是对象的其他数据,因为笔者比较懒,不想画图了,画图工具太伤。。。。)

4、本地方法栈

其实这个区域和JVM栈功能差不多,区别就是,JVM栈存放java方法的各种数据,本地方法栈存放native方法各种数据。

5、程序计数器

这个区域唯一的作用就是,存放下一条将要运行的指令的地址。

大概就这么多,其实最主要的就是平常说的最多的堆和栈,另外稍微解释了一下java的参数传递和一个经常看到的错误,如果有什么不懂的,大家可以留言,如果有什么不对的,也希望大神指正。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章