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

JVM的基本结构图:

c06b948d1f6cae62cc04e524e2d47aa8.png

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

JVM栈 (Java Virtual Machine Stacks)

堆内存 (Heap Memory)

方法区 (Method Area)

本地方法栈 (Native Method Stacks)

程序计数器 (Program Counter (PC) Register)

一、JVM栈

程序是在栈内存中运行的,所以栈内存解决的是程序运行时的问题

Java以栈帧为单位保存线程的运行状态,虚拟机只会对栈执行两种操作:以栈帧为单位的压栈或者出栈

一个线程独占一个Java栈(栈里的数据是线程私有的)

存储的是基本数据类型和堆中数据的引用(引用地址)

分为三个部分:基本类型变量区、执行环境上下文、操作指令区

异常:java.lang.StackOverFlowError

二、堆

堆内存解决的是数据存储的问题

所有线程共享java堆

存储的是对象和数组(对象本身)

动态的分配内存(运行时分配),生命周期(不确定)不需要预先告诉编译器,Java的垃圾回收机制会自动收走不使用的数据

由于运行时动态分配内存,存储数据较慢

异常:java.lang.OutOfMemoryError

三、方法区

又称静态区

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

四、本地方法栈

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

五、程序计数器

用于保存当前线程执行的内存地址

由于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);

}

}

复制代码启动虚拟机进程,程序从AppMain的开始,先从classpath中找到并读取AppMain.class二进制文件(编译后),然后把AppMain类的类信息和方法信息放入方法区,这个过程叫AppMain类的加载过程;

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

Sample test1 = new Sample("测试1");

复制代码

接着Java虚拟机到方法区中查找Sample类的信息,没有找到,然后通过步骤1重新加载Sample类到方法区;

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

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

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

六、疑问区

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

A:

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

对象传递是引用值传递,原始类型数据传递是值传递;

实际上这个传入函数的值是对象引用的拷贝,即传递的是引用的地址值,所以还是按值传递。

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

A:

Object obj = new Object();

复制代码

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

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

复制代码

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

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

七、拓展

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

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

Sample sample = new Sample();

复制代码

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

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

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

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

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

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

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收

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

参考地址:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值