JVM堆 栈 方法区详解

一、栈

每当启用一个线程时,JVM就为他分配一个JAVA栈,栈是以帧为单位保存当前线程的运行状态

栈是由栈帧组成,每当线程调用一个java方法时,JVM就会在该线程对应的栈中压入一个帧

只有在调用一个方法时,才为当前栈分配一个帧,然后将该帧压入栈

栈帧帧是由局部变量区、操作数栈和帧数据区组成

java栈上的所有数据都是私有的,任何线程都不能访问另一个线程的栈数据

局部变量区  调用方法时,类型信息确定此方法局部变量区和操作数栈的大小

  局部变量区被组织为以一个字长为单位、从0开始计数的数组,类型为short、byte和char的值在存入数组前要被转换成int值,而long和double在数组中占据连续的两项,在访问局部变量中的long或double时,只需取出连续两项的第一项的索引值即可,如某个long值在局部变量区中占据的索引时3、4项,取值时,指令只需取索引为3的long值即可

runInstanceMethod的局部变量区第一项是个reference(引用),它指定的就是对象本身的引用,也就是我们常用的this

但是在runClassMethod方法中,没这个引用,那是因为runClassMethod是个静态方法。

 操作数栈  可把操作数栈理解为存储计算时,临时数据的存储区域。

  和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。

  但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的。

  int a = 100;

  int b = 98;

  int c = a + b;

  

帧数据区   java栈帧通过帧数据区存的数据来支持常量池解析、正常方法返回以及异常派发机制

  当JVM执行到需要常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它。

  处理java方法的正常结束和异常终止:如果是通过return正常结束,则当前栈帧从Java栈中弹出,恢复发起调用的方法的栈。如果方法有返回值,JVM会把返回值压入到发起调用方法的操作数栈。

  为了处理java方法中的异常情况,帧数据区还必须保存一个对此方法异常引用表的引用。当异常抛出时,JVM给catch块中的代码。如果没发现,方法立即终止,然后JVM用帧区数据的信息恢复发起调用的方法的帧。然后再发起调用方法的上下文重新抛出同样的异常。

  

1.Java中的基本数据类型不一定存储在栈中,如果该变量是基本数据类型则存储在栈中,

  该变量是一个对象(如:int[] array=new int[]{1,2};),则在堆中。

2.为什么函数调用要用栈实现?

  函数调用的局部状态之所以用栈来记录是因为这些数据的存活时间满足“后入先出”(LIFO)顺序,而栈的基本操作正好就是支持这种顺序的访问

二、堆

  (不再造轮子,出门左转  JVM入门——JVM内存结构  https://www.cnblogs.com/hexinwei1/p/9406239.html)

三、方法区

  用于存储虚拟机加载的:静态变量+常量+类信息+运行时常量池

  类信息:类的版本、字段、方法、接口、构造函数等描述信息

  运行时常量池用于存储 Java 类文件常量池中的符号信息

  运行时常量池中保存着一些 class 文件中描述的符号引用,同时还会将这些符号引用所翻译出来的直接引用存储在运行时常量池中

HotSpot 方法区变迁

  在 JDK1.2 ~ JDK6 的实现中,HotSpot 使用永久代实现方法区

  JDK7+ 移除永久代  字符串常量和类引用被移动到 Java Heap中

  Oracle 同时收购了 BEA 和 Sun 公司,同时拥有 JRockit 和 HotSpot,在将 JRockit 许多优秀特性移植到 HotSpot 时由于 GC 分代技术遇到了种种困难,所以从 JDK7 开始 Oracle HotSpot 开始移除永久代

影响示例:

1. 

public class Test2 {
    public static void main(String[] args) {
        /**
         * 首先设置 持久代最大和最小内存占用(限定为10M)
         * VM args: -XX:PermSize=10M -XX:MaxPremSize=10M
         */

        List<String> list  = new ArrayList<String>();

        // 无限循环 使用 list 对其引用保证 不被GC  intern 方法保证其加入到常量池中
        int i = 0;
        while (true) {
            // 此处永久执行,最多就是将整个 int 范围转化成字符串并放入常量池
            list.add(String.valueOf(i++).intern());
        }
    }
}

  将整个 int 范围转化成字符串并放入常量池
  JDK1.6常量池在方法区(永久带),设置了最大内存为10M,导致Perm 内存溢出
  JDK1.7及以后常量池在堆中,代码执行不会有问题

2.

public class Test1 {
    public static void main(String[] args) {

         String s1 = new StringBuilder("漠").append("然").toString();
         System.out.println(s1.intern() == s1);

         String s2 = new StringBuilder("漠").append("然").toString();
         System.out.println(s2.intern() == s2);

    }

}

JDK6 下执行结果为 false、false

在 JDK7 以上执行结果为 true、false

1.在 Java 中直接使用双引号展示的字符串将会在常量池中直接创建
2.String的intern方法首先将尝试在常量池中查找该对象,如果找到则直接返回该对象在常量池中的地址;找不到则将该对象放入常量池后再返回其地址
3. s1--对象堆中的地址
  s1.intern()--对象常量池中的引用(JDK1.6方法区 JDK1.7堆)
  ==>JDK1.6 false JDK1.7 true
4.JDK1.7 s2.intern() == s2 false原因:
  s2--新对象s2堆中的地址
  s2.intern()--首先尝试在常量池中查找该对象,找到s1(s1.intern()已经将该对象放入常量池),返回s1的地址

参考资料

1.深入JVM——栈和局部变量  http://xtu-tja-163-com.iteye.com/blog/775987

2.Java堆和栈看这篇就够了  https://iamjohnnyzhuang.github.io/java/2016/07/12

3.JVM入门——JVM内存结构  https://www.cnblogs.com/hexinwei1/p/9406239.html

4.Java 内存之方法区和运行时常量池  https://mritd.me/2016/03/22/Java-%E5%86%85%E5%AD%98%E4%B9%8B%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C%E8%BF%90%E8%A1%8C%E6%97%B6%E5%B8%B8%E9%87%8F%E6%B1%A0/

原文地址:https://www.cnblogs.com/hexinwei1/p/9414018.html

时间: 2024-10-11 10:17:12

JVM堆 栈 方法区详解的相关文章

Jvm(27.14.2),理解升级---堆,栈,方法区

看完GC的回收策略之后,我们再来看一下堆,栈,方法区的交互. 首先我们必须牢记一句话,栈是堆和方法区的引用,学的越多对这句话的理解要越深. 1,这里的堆主要是对局部变量表来说的. 2,栈的内存地址是远远小于堆得,因为在栈中只是对象的引用. 3,gc回收只是回收堆内存,不用考虑栈的内存,因为栈的数据结构就是一旦出栈就会释放的. 栈也是JAVA虚拟机自动管理的,(不是由gc)栈类似一个集合(不过是有固定的容量),是由很多元素(专业术语:栈帧)组合起来的,在我们码代码的时候,每调用一个方法,在运行的时

JVM 运行时数据区详解

一.运行时数据区: Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同数据区域. 1.有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,所有的线程共享这些数据区. 2.第二种则是与线程一一对应,随线程的开始和结束而创建和销毁,线程之间相互隔离. java虚拟机所管理的内存将会包括以下几个运行时数据区域 二.数据区详解 1.程序计数器(Program Counter Register) 也叫PC寄存器是一块较小的内存空间,它的作用是存储当前线程所执行的字节码的信号指示器.

Jvm(14.2),运行时数据---堆,栈,方法区

一,我们首先来看张图 二,代码来解释 先了解具体的概念: JAVA的JVM的内存可分为3个区:堆(heap).栈(stack)和方法区(method) 堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身栈区: 1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对 象),对象都存放在堆区中 2.每个栈中的数据(原始类

java 堆 栈 方法区的简单分析

Java里的堆(heap)栈(stack)和方法区(method) 基础数据类型直接在栈空间分配, 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收.   引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量 . 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完成后从栈空间回收.局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收. 方法调用时传入的

运行时数据区域(堆 栈 方法区 常量池)和内存分配策略

内存管理 内存分配和内存释放 内存分配由程序完成,内存释放由GC完成 运行时数据区域 (1)程序计数器(program counter register) 一块较小的内存空间 当前线程所执行的字节码的行号指示器,字节码解释器在工作的时候就是通过改变程序计数器的值来选取下一跳要执行的指令 多线程环境下,线程轮流切换执行,程序计数器保证线程切换之后能恢复到正确的位置 每个线程都有一个独立的程序计数器 线程私有 没有任何异常 java方法,程序计数器的值为当前正在执行的虚拟机字节码指令的地址 nati

Java运行时数据区域(堆 栈 方法区 常量池)

运行时数据区域 (1)程序计数器(program counter register) 一块较小的内存空间 当前线程所执行的字节码的行号指示器,字节码解释器在工作的时候就是通过改变程序计数器的值来选取下一跳要执行的指令 多线程环境下,线程轮流切换执行,程序计数器保证线程切换之后能恢复到正确的位置 每个线程都有一个独立的程序计数器 线程私有 没有任何异常 (2)虚拟机栈(stack) 虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的过程中都会创建一个栈帧,用于存储局部变量表.操作数栈.动

JVM 栈堆和方法区

栈区 栈区描述的是方法执行的内存模型.每个方法在执行时都会创建一个栈帧(存储局部变量.操作数栈.动态链接.方法出口等) JVM为每个线程创建一个栈,栈属于线程私有,不能实现线程间的共享,用于存放该线程执行方法的信息(实际参数.局部变量等) 基本类型变量去,执行环境上下文,操作指令区(存放操作指令) 堆区 堆用于存储创建好的对象和数组(数组也是对象) JVM只有一个堆,被所有线程共享 堆是一个不连续的内存空间,分配灵活,速度慢! 方法区 Jvm只有一个方法区,被所有线程公用 存放整个程序中唯一的元

C/C++堆、栈及静态数据区详解

转自:https://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html  做略微修改 本文介绍C/C++中堆,栈及静态数据区. 五大内存分区 在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和常量存储区.下面分别来介绍: 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区.里面的变量通常是局部变量.函数参数等. 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用

从几个sample来学习JAVA堆、方法区、JAVA栈和本地方法栈

最近在看<深入理解Java虚拟机>,书中给了几个例子,比较好的说明了几种OOM(OutOfMemory)产生的过程,大部分的程序员在写程序时不会太关注Java运行时数据区域的结构: 感觉有必要通过几个实在的例子来加深对这几个区域的了解 1)Java堆 所有对象的实例分配都在Java堆上分配内存,堆大小由-Xmx和-Xms来调节,sample如下所示: [java] view plaincopyprint? public class HeapOOM { static class OOMObjec