JVM 运行时数据区域

C语言的阴影面试

还记得刚进大学的时候,觉得这个世界上最难学的不过C语言了。尽管后来陆续学了不少的更难的课程,尽管慢慢掌握了计算机的不少原理以后,回头来看C语言,彷佛没那么难理解,可当年初学C语言时的“阴影”,这么多年来,一直没有散去。数组

我常常还能想到几年前,懒散的趴在逸夫教学楼F1教室最后一排的座位上,听兰书敏老师讲着“戏院”(C语言)的场景。兰老师问到:“大家怎么都不吭声?究竟是哪里听不懂?”老师,学生当时真是哪哪儿都没听懂啊。安全

 

身在Java,心在C(Java大神勿喷,C对我来讲,真是一种情怀)多线程

没想到,工做一年多的时间里,用的最多的语言不是对我影响最大的C,而是大学毕业以后现学现卖的Java。因此我对C和Java都算有一点了解。工具

 

一条有意思的Java面试题this

前几天在搜索一个问题的解决方案时,偶然看到一个Java面试题,以为网上绝大多数解释,有些浮于表面。而真神们又不屑于解释这些无聊的问题,因此以为有必要站在一个“双修(残废)”者的角度,谈谈这个问题。spa

 

Java内存分配线程

在解释这个问题以前,我想简单的记录一下Java虚拟机对内存的分配管理。3d

网上有不少关于Java内存管理的讲解,但不知道为何,大多数做者并无系统的讲解,有些过于散碎。指针

咱们先来看看这张图(我不会画图,画的太丑,各位受累受累了)。简单的说,Java运行时内存区域,就由上面几部分构成。青绿色标记的,是每一个线程私有的内存区域,其余的为线程共享的内存区域。咱们先简单的依次说明每一个部分是用来存什么的,最后再用一个简单的例子,将各个部分结合起来简单介绍其内存分配的基本过程。

首先,程序计数器(pc)。这个东西对于不少开发者来讲,再熟悉不过了,尽管不一样领域的pc,具体用法上存在一些小小的差别,但总的来讲,pc是用来记录程序运行到哪里了,下一步又该执行哪一步操做。pc占据的内存是线程级的,即随线程的建立而产生,随线程的销毁而销毁(被回收)。

其次JVM栈和本地方法栈。这两个栈在存储结构上,基本相同,以致于不少的JVM产商,将两者合而为一。JVM栈,顾名思义,是用来存储Java方法运行过程当中使用的栈数据,本地方法栈就是用来存储本地方法执行过程当中的栈数据。栈中存储的数据,是一种被称为“栈帧”的东西。栈帧主要包括:局部变量表和操做数栈。栈帧的入栈和出栈,分别意味着一个方法的执行与结束。

接着,咱们来看看方法区。方法区主要是用来存类型数据的,与类型相关的东西,好比常量,静态变量,编译后的代码等,基本都存储在这一区域。而由于“无用类”的判断条件很是苛刻(有三点,第一,该类无可达对象,第二,该类的ClassLoader已被回收,第三,该类的Class对象无引用),这个区域存储的内容很难会被回收,因此你可能会在不少地方看到“永久代”一词,其实说的主要也就是这个方法区。方法区中,有个特殊的区域,被划分(逻辑划分,不必定为物理划分)出来,即“运行时常量池”。运行时常量池,保存着字面量,符号引用等。方法区是线程共享的,随JVM启动而建立,JVM退出而销毁。

最后,是这个堆。堆,在不少领域也有用到。在Java中,堆,是用来存储对象的相关内容,包括对象的对象头和实例数据(数组对象还有一个数组的长度)。不一样的JVM实现,对象可能还包括类型指针(指向对象所属的类型信息,存在方法区中)和占位符(虚拟机实现可能须要内存对齐)等。

 

一个简单的例子

public void test (int result, int num) {
    TestClassB classB = new TestClassB();
    classB.methodB(); 
}

public class TestClassB {
    public void methodB(result, num) {
        int finalResutl = result + num;
        ......
    }
}

//author: Feng_zhulin
//http://cnblogs.com/zhulin-jun

如今假设线程A在执行test方法,并已经执行到TestClassB classB = new TestClassB()。首先,会去判断类TestClassB有没有被加载到方法区中,若是没有,先加载类(类的加载过程不详细说明,有空能够写篇Java类加载过程的博客)入方法区;而后由于执行的是new操做,须要建立一个对象,这时候须要在堆上申请内存(内存分配有不少方案,须要考虑多线程下的线程安全问题等诸多因素,不详细阐述),用于存放对象的相关数据(对象头,实例数据,类型指针,占位符等);再而后为TestClassB的成员赋“零值”(不一样类型的数据,零值不一样,基本数据类型int的零值为0,引用类型的零值为null,等);最后,设置对象头。这样对于JVM来讲,对象就建立成功了(后面就是执行类的构造方法了,那是属于Java语言层面的建立对象的过程)。

上面总说起一个叫作“对象头”的东西,这个东西跟对象自己没有什么关系,存储的是对象的运行时数据,包括对象的hashcode,对象的锁状态,对象持有的锁等等。好比对象的hashcode,用于指定对象的惟一性,在GC和对象定位等过程当中都会用到。

 

接着,pc加一(此处加一,表示的是加上一个JVM指令的位数,表示的是下一个指令的内存地址),执行下一步:classB.methodB();这是一个方法调用。正如上面所说,方法的执行和结束,意味着方法栈中,栈帧的进栈和出栈。

 

好滴好滴,又到看图的时候了(捂脸,我不只不会画图,尚未好用的画图工具,求推荐mac的良心画图工具,若是不是免费的,我只接受有破解版的)。对象在堆中存放,然而,对象的操做,方法的执行,就进入了“栈”。调用methodB()时,methodB()栈帧进栈,栈帧包含局部变量表和操做数栈。由于这个地方的methodB()不是类方法,因此,局部变量表的第一个变量为调用该方法的类,即classB(this)。操做数栈用于进行当前数据操做,操做结果出操做数栈,并保存进局部变量表。

例子就这样简单的结束了,总的来讲,就是类进入方法区,建立的对象在堆中,方法执行的时候,在方法栈中。

 

下面,咱们来看这个有意思的Java面试题。

当一个对象被看成参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里究竟是值传递仍是引用传递?

网上的标配答案:是值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例做为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性能够在被调用过程当中被改变,但对对象引用的改变是不会影响到调用者的。

其实这确实是一个很无聊的问题,原本也没有太当回事,可是一来,这个问题下面的追问者不少,我查了下知乎,对这个问题的提问者和回答人也不少;二来,答案不够准确,或者说是,没讲到点子上,有人甚至拿《Java核心卷》里的三句话做为答案。

《Java核心卷》对这种问题有以下三句话的描述:

1.一个方法不能修改一个基本数据类型的参数
2.一个方法能够改变一个对象参数的状态
3.一个方法不能让对象参数引用一个新的对象

无可厚非,这三句话总结的很经典,可是这只是简单的说出告终论,缘由呢?就用这三句话解释这个问题,给初学者带来的感受,只是,哦,原来Java还有这么一个定理(限制)。那么一个个由JVM规范致使的结果,都成了须要死记硬背的“定理”。

public class Program {
        public static void swap(String x, String y) {
            String temp = x;
            x = y;
            y = temp;
        }

        public static void main (String[] args) {
            String a = "testa";
            String b = "testb";
            swap (a, b);
        }
}
//author: Feng_zhulin
//http://cnblogs.com/zhulin-jun

咱们接着看这段代码,将它还原到内存中。

图中“0x”开头的是十六进制的内存地址,随便举的例子。在main()方法调用swap()方法的时候,只是将main的局部变量表中的a和b的值(指向运行时常量池的地址)拷贝到swap的局部变量表中的x和y,在swap的局部变量表中进行的换值操做,并未对main局部变量表起做用,因此,在swap退出前,x的值是“testb”, y的值是“testa”,x与y的值互换了,但a与b的值并无所以而改变。固然,swap退出以后,相应的局部变量表会被回收,也就没有所谓的x和y了。

这是这个问题所真正涉及的知识点,我很认同知乎上那位朋友的话,没有必要非得分出个所谓的“值传递”和“引用传递”。

这边我顺便提一点在C中,是怎么作到交换上面例子中a和b这两个值的。

在C中有一个很神奇的东西,名字叫“指针”。能够很简单的认为,它就是地址。那么“指针的指针”,就是“地址的地址”。上面以“0x”开头的数据,就是内存地址,若是将这个地址赋值给一个C中的变量,那么这个变量就称为指针变量。那么咱们彻底能够经过指针,透过中间变量,直接操做a和b中存储的内容(此处说的是地址),甚至是直接操做到“testa”和“testb”。

C语言因指针而美丽,却也因指针而复杂。Java解决了C中内存须要开发者本身管理的问题,也去除了指针的概念,让程序出错的几率大幅度下降,却也由于没有指针,在我这种装了两个半桶浆糊的人眼中,不少地方变的难以想象的臃肿和麻烦。

相关文章
相关标签/搜索