栈(stack)

Java 里的栈分为 虚拟机栈与本地方法栈,在大家口中经常说的栈,一般是指我们的虚拟机栈

虚拟机栈

Java 虚拟机栈(后面简称栈)是线程私有的,所以他的生命周期与当前线程是一样的,栈是用来描述方法执行的一个内存模型,因为每个方法在执行的同时,都会创建一个栈帧,而这个栈帧里面,又存储着局部变量表,操作数栈,动态链接,方法出口等一系列信息,下图为一般调用某一个方法时的栈内存图

java 弹出栈顶元素 栈 java里的栈_Java堆、栈解析

最里层的局部变量表,存放了编译期可知的各种数据类型(八种Java的基本类型)、某一对象的引用(对象实例存于堆中,这里只是存了变量的一个引用地址,该地址有可能指向的还是地址,并不是对象本身),除了上图没出现的外,这里还存有return Addre类型,即指向一条字节码指令的地址,他们都遵循着先入后出的规则存放。

因为里面放的基本上是基本类型,和一些用类型的地址,所以在编译器,栈就可以知道分配多大的内存给他们,因为除了向double、long类型的数据占用2和局部变量空间(Slot)外,其余数据类型都都只是占用一个空间,这样一个栈帧分配的局部变量的空间大小的确定的,且在运行期间不会改变,这样就导致了对栈内数据的提取速度较快

本地方法栈(Aative Method Area)

上面所讲的虚拟机栈和这个其实差不多,区别就是,上面的虚拟机栈是用于虚拟机执行Java方法服务,而本地方法栈是用来执行本地方法的,因为Java是托管代码,所以在最底层一般调用的都是会变或者C的方法,这时就不管虚拟机栈什么事了,而是本地方法栈大显身手的时候,,但是由于本地方法栈里面的方法使用的语言、数据结构等并没有强制规定,这样虚拟机就可以自由实现他,所以有的虚拟机(比如Sun HotSpot)就直接虚拟机栈和本地方法栈合二为一了

当一个数据占用内存大小确定、具有生命周期、且先进后出时,就会被存于栈中

方法区

方法区也被称为静态区,是线程共享的内存区域,用于存储不如类信息class、常量 final、静态数据 static、以及全局变量等数据;因为方法区是被所有线程共享的,所以必须考虑数据的线程安全问题

java 弹出栈顶元素 栈 java里的栈_Java方法区的解析_02


方法区又可以分为上图区域

  • 类类型信息:父类名称、类型修饰符、类类型的直接接口
  • 常量池:比如static数据,全局变量,final常量
  • 域信息:域名称、类型、修饰符
  • 方法信息:方法名称、参数、修饰符等

Java虚拟机对方法区的限制非常宽松,它不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾回收,该区域的垃圾回收主要针对常量池和类型的卸载,但是效果却不那么好

JDK 1.7之后,String以及不属于方法区常量池了,而是移到了堆区域

堆(heap)

堆是线程共享的区域,即也需要考虑到线程安全的问题,它是虚拟机里面最大的一块内存,这里的内存是用来存放实例化对象,几乎所有的实例化对象和数组都是存在这里,简单的理解就是只要看到 new ,就应该知道 new 出来的那一块内存在堆里面,由于垃圾回收期的原因,堆一般又分为新生代和老年代,而新生代又分为 Eden空间、From Survivor空间和To Survivor,他们的比例大概为8:1:1,而新生代与老年代的比例大概为1:2,为了更好的回收内存或者更快的分配内存,堆又划分出了多个线程私有的分配缓冲区(TLAB),但是无论怎么划分,堆里面还是存放的实例化对象或者数组
由于虚拟机不需要知道堆内存存放的变量信息空间大小是多少,也不需要知道每个对象的声明周期,所以该区域的程序的运行灵活性高,堆还是GC重点回收的区域

  • 老年代:老年代代里存放的都是存活时间较久的,大小较大的对象
  • Eden:新对象分配内存的地方,由于堆是所有线程共享的,因此在堆上分配内存需要加锁
  • From 和 to Survivor:当发生Minor GC时,Eden区和Survivalfrom区会把一些仍然存活的对象复制进Survival to区,并清除内存。Survival to区会把一些存活得足够旧的对象移至年老代
//关于String,在JDK1.7之前String都是在方法区的常量池里面,但是1.7后,就将其移出来了
package com.qcby.demo;


import java.util.ArrayList;
import java.util.List;

public class Main {


    public static void main(String[] args) {

        List<String> str = new ArrayList<>();
        for(int i = 0;i < 1000000000;i++)
        {
            str.add(String.valueOf(i).intern());//String.intern()会一直往字符串常量池添加新字符串
        }

    }

}

java 弹出栈顶元素 栈 java里的栈_Java方法区的解析_03