Java虚拟机专题之内存管理(读书笔记)
一 Java虚拟机运行时的内存数据区域
二 为什么有线程共享区和线程独占区
我们知道,Java程序在JVM中运行,有的内存对象声明周期长,不随线程的释放而释放,比如堆和方法区;但是有些内存对象,就是线程私有范围的,随着线程的结束而结束,该部分使用的内存也会被释放。当然这也是系统垃圾回收的场所只发生在线程共享区域的原因。
三 程序计数器
程序计数器是一块较小内存空间,他可以被看做是当前线程所执行的字节码的行号指示器
程序计数器位于线程独享区域
如果线程执行的是Java方法,这个计数器记录的正在执行虚拟机字节码指令;如果正在执行本地方法(Native Method),这个计数器的值为undefined。
四 虚拟机栈
虚拟机栈主要是存储Java方法执行时的一些局部变量表,操作数栈,动态链接等信息的内存区域。
4.1 局部变量表
局部变量表是存储方法参数和方法局部变量的存储空间。也就是说他存放了一些基本数据类型,诸如boolean,byte,char,short,int,float,long,double,对象引用reference和返回地址returnAddress。Reference不等价于对象本身,它可能是一个指向对象起始地址的引用指针;retyurnAddress指向了一条字节码指令的地址。
4.2 操作数栈
和局部变量表一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的。
五 本地方法栈
本地方法栈其实和虚拟机栈差不太多,他们很相似,区别在于:
虚拟机栈为虚拟机执行Java方法服务,二本地方法栈则是为虚拟机使用到的Native方法服务的
六 方法区
方法区主要用于存放JVM加载的类信息,诸如类字段,类方法,接口等,以及常量池和静态变量等,常量池存储着字面量和符号引用。
方法区被所有线程共享。
七 堆
堆也是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,主要存放对象实例。
Java堆是垃圾收集器管理的主要区域,因此有时候也叫作GC堆。
为什么管理的主要区域,因为线程共享。不像线程私有的内存区域,随着线程的结束而释放掉了内存。
八 常量池
注意:JDK1.7以前,常量池位于方法区;JDK1.7以后,常量池位于堆中,jdk1.8甚至取消了PERM区,把类信息等放入了元数据区,这块内存区间不是属于Java虚拟机,而是属于Java虚拟机之外的内存的。
其实都是说的一个东西,我们知道常量池是方法区的一块内存区域,专门用于存放字符串常量和其他一些字面量。所以当年你通过直接把字符串赋值给一个变量,那么这个字符串就是放在这个常量池的,而不是堆中的,由于常量池只会维护一个唯一的常量,所以常量类似于Set类型,没有重复值,所以无论有多少变量的常量是相同的,他们都是指向的常量中的这个引用。
举个例子:
String v1 = "abc";
String v2 = "abc";
// 因为都是指向常量池的abc的引用,所以两者相等
System.out.println(v1 == v2);
但是,如果这个字符串是你new出来的,那么他会创建2个对象,一个位于常量池中,一个位于heap堆中
String v1 = "abc";
String v2 = "abc";
// 因为都是指向常量池的abc的引用,所以两者相等
System.out.println(v1 == v2);
String v3 = new String("abc");
// 一个指向常量池的abc的引用,一个指向堆中对象,所以两者不相等,这里应该是false
System.out.println(v1 == v3);
通过String.valueOf构造出来的字符串,也是字面值,不是new出来的对象,内部其实就是调用了object的tostring方法而已
String v1 = "abc";
String v2 = "abc";
// 因为都是指向常量池的abc的引用,所以两者相等
System.out.println(v1 == v2);
String v3 = new String("abc");
System.out.println(v1 == v3);
String v4 = String.valueOf("abc");
System.out.println(v1 == v4);
字节码常量池和运行时常量池比较
字节码常量池: 就是我们直接使用双引号引起来的字符串,即静态的,在编译时就确定的常量值。
运行时常量池: 就是我们在运行时才能确定的常量值,比如加号拼接的字符串。