java虚拟机(JVM)堆、栈、方法区的介绍

JVM的基本结构图:javascript

由图可知,JVM的内存区域主要能够划分为5块:java

  1. JVM栈 (Java Virtual Machine Stacks)
  2. 堆内存 (Heap Memory)
  3. 方法区 (Method Area)
  4. 本地方法栈 (Native Method Stacks)
  5. 程序计数器 (Program Counter (PC) Register)

1、JVM栈

  • 程序是在栈内存中运行的,因此栈内存解决的是程序运行时的问题
  • Java以栈帧为单位保存线程的运行状态,虚拟机只会对栈执行两种操做:以栈帧为单位的压栈或者出栈
  • 一个线程独占一个Java栈(栈里的数据是线程私有的)
  • 存储的是基本数据类型和堆中数据的引用(引用地址)
  • 分为三个部分:基本类型变量区、执行环境上下文、操做指令区
  • 异常:java.lang.StackOverFlowError

2、堆

  • 堆内存解决的是数据存储的问题
  • 全部线程共享java堆
  • 存储的是对象和数组(对象自己)
  • 动态的分配内存(运行时分配),生命周期(不肯定)不须要预先告诉编译器,Java的垃圾回收机制会自动收走不使用的数据
  • 因为运行时动态分配内存,存储数据较慢
  • 异常:java.lang.OutOfMemoryError

3、方法区

  • 又称静态区
  • 存储每一个类的信息(包括类名、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码

4、本地方法栈

和java栈的做用差很少,只不过是为JVM使用到的native方法(使用非Java语言实现的方法)服务的数组

5、程序计数器

  • 用于保存当前线程执行的内存地址
  • 因为JVM程序是多线程执行的(线程轮流切换),因此为了保证线程切换回来后,还能恢复到原先状态,就须要一个独立的计数器,记录以前中断的地方,可见程序计数器也是线程私有的
  • 注意这个区域是惟一一个不抛出OutOfMemoryError的运行时数据区

下面经过AppMain.java和Sample.java两块代码进一步说明多线程

//运行时, jvm把AppMain的信息都放入方法区
public class AppMain {
    //main 方法自己放入方法区
    public static void main(String[] args) {
        //test1是引用,因此放到栈区里,Sample是自定义对象应该放到堆里面
        Sample test1 = new Sample("测试1");
        Sample test2 = new Sample("测试2");
        test1.printName();
        test2.printName();
	}
} 
复制代码
//运行时, jvm把Sample的信息都放入方法区
public class Sample {
    //new Sample实例后, name 引用放入栈区里, name 对象放入堆里
    private String name;      

    /** 构造方法 */
    public Sample(String name) {
        this.name = name;
    }

    /** 输出 */
    //print方法自己放入方法区里
    public void printName() {
    	System.out.println(name);
    }
} 
复制代码
  1. 启动虚拟机进程,程序从AppMain的开始,先从classpath中找到并读取AppMain.class二进制文件(编译后),而后把AppMain类的类信息和方法信息放入方法区,这个过程叫AppMain类的加载过程;jvm

  2. Java虚拟机定位到方法区AppMain类中的main()方法的字节码,开始执行它的指令,第一条语句是:函数

    Sample test1 = new Sample("测试1");
    复制代码
  3. 接着Java虚拟机到方法区中查找Sample类的信息,没有找到,而后经过步骤1从新加载Sample类到方法区;测试

  4. 在堆中为Sample对象实例分配内存,这个实例持有指向方法区的Sample类的信息的引用(引用是指Sample类的信息在方法区中的内存地址)this

  5. 每个线程都有一个栈,栈里面的元素被称为栈帧,每当线程调用一个方法的时候就会往栈里压入一个新帧,这里的帧是用来存储方法的参数、局部变量和运算过程当中的临时数据。位于**“=”前的test1是一个在main()方法中定义的变量,它是一个局部变量,所以,它被会添加到了执行main()方法的主线程的java方法调用栈中,而“=”**将把这个test1变量指向堆区中的Sample实例,也就是说,它持有指向Sample实例的引用spa

  6. 接下来,JAVA虚拟机将继续执行后续指令,在堆区里继续建立另外一个Sample实例,而后依次执行它们的printName()方法。当JAVA虚拟机执行test1.printName()方法时,JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,从而得到printName()方法的字节码,接着执行printName()方法包含的指令.net

6、疑问区

一、Q: Java中的参数传递(传值呢?仍是传引用?)

A:

程序运行永远都是在栈中进行的,于是参数传递时,只存在传递基本类型和对象引用的问题,不会直接传递对象自己;

对象传递是引用值传递,原始类型数据传递是值传递; 实际上这个传入函数的值是对象引用的拷贝,即传递的是引用的地址值,因此仍是按值传递

二、Q: Java对象的大小如何计算?

A:

Object obj = new Object();
复制代码

这样在程序中完成了一个java对象的声明,obj对象所占的空间为:

4byte(java栈中保存引用的所须要空间)+ 8byte(java堆中对象所需的空间) = 12byte
复制代码

全部的java非基本类型的对象都须要默认继承Object对象,所以不论什么样的java对象,其大小都必须是大于8byte

同时java对象大小是8的整数倍,所以obj对象的大小至少为16byte

7、拓展

对象引用类型分为强引用软引用弱引用虚引用

一、强引用:声明对象时虚拟机生成的引用

Sample sample = new Sample();
复制代码

sample为强引用,不会被垃圾回收

二、软引用:根据系统剩余内存来决定是否须要回收

换句话说,虚拟机在发生java.lang.OutOfMemoryError时,确定是没有软引用存在的

三、弱引用:弱引用与软引用相似,但在进行垃圾回收时,是必定会被回收掉的

所以其生命周期只存在于一个垃圾回收周期内

四、虚引用虚引用并不会决定对象的生命周期。

若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收

虚引用主要用来跟踪对象被垃圾回收器回收的活动


参考地址

blog.csdn.net/rodbate/art…

相关文章
相关标签/搜索