需要储备的知识点
Java 中的变量根据不同的标准可以分为两类,以其引用的数据类型的不同来划分可分为 “原始数据类型变量和引用数据类型变量”,以其作用范围的不同来区分可分为 “局部变量,实例变量和静态变量”。
实例方法和普通方法
程序| 进程 | 线程
- JVM 是一份本地化的程序,本质上是可执行的文件,是静态的概念。程序运行起来成为进程,是动态的概念。
- JAVA进程( java 程序):一个 JVM 实例其实就是 JVM 跑起来的进程。
- 各个 JVM 实例之间是相互隔离的。eg:登录了多个QQ
- 进程:是并发执行的程序在执行过程中分配和管理资源的基本单位。
- 线程:线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。线程之间共用一个进程的内存空存空间 (即 jvm 堆内存),各个线程也有自己独立专有的内存空间 (即 jvm 栈帧空间)。看下图就明白了
首先确定一下jkd1.8之后的JVM内存模型
方法变量内存分配图
1 | class Fruit { |
结合上图可得:
- 每个 Java 方法在被调用的时候都会创建一个栈帧,并入栈。一旦完成调用,则出栈。所有的的栈帧都出栈后,线程也就完成了使命。
本地方法栈
- 本地方法栈为虚拟机使用到的 native 方法服务
JAVA Stack
栈中有栈帧,栈帧内存:
- 8 中基本类型变量 (byte(8)、short(16)、int(32)、long(64)、float(12)、double(64)、char(16)、boolean;)
- 对象的引用变量(Fruit fruit ) 、
- 实例方法(方法出口)、
- 局部变量表;
- 操作数栈(程序计数器)、
Java.lang.StackOverflowError 栈内存溢出
堆
一个JVM只存在一个堆内存,堆内存的大小是可以调节的。
- 储存:运行时常量池(字符串拼接得到的P141)、实例变量eg:new Fruit、(也叫成员变量)、静态变量
- 两个区域:新生代(8:1:1)、老年代()
PCR
Java 虚拟机可以支持多个线程同时执行,每个线程都有自己的程序计数器。在任何时刻,每个线程都只会执行一个方法的代码,这个方法称为该线程的当前方法(current method)。
如果线程正在执行的是 Java 方法(不是 native 的),则程序计数器记录的是正在执行的 Java 虚拟机字节码指令的地址。如果正在执行的是本地(native)方法,那么计数器的值是空的(undefined)。
元空间(以前的永久代/方法区)
Caused by: java.lang.OutOfMemoryError: Metaspace
储存信息:
- 类加载器读取了类文件后,需要把Class metadata(类元数据)、方法模板(构造方法)、常变量放到元空间中;
Java 中有哪几种常量池?
class 文件常量池、运行时常量池、字符串常量池。
class 文件常量池
class 文件常量池(class constant pool)属于 class 文件的其中一项,class 类文件包含:类的版本、常量池、访问标志、字段表集合、方法表等信息。
常量池用于存放编译期间生成的各种字面量(Literal)和符号引用(Symbolic References)。
运行时常量池
class 文件常量池是在类被编译成 class 文件时生成的。而当类被加载到内存中后,JVM 就会将 class 文件常量池中的内容存放到运行时常量池中。
Java 虚拟机规范中对运行时常量池的定义如下:
A run-time constant pool is a per-class or per-interface run-time representation of the constant_pool table in a class file.
运行时常量池是 class 文件中每一个类或接口的常量池表(constant_pool table)的运行时表示形式。
因此,根据规范定义,可以说运行时常量池是 class 文件常量池的运行时表示,每个类在运行时都有自己的一个独立的运行时常量池。
字符串常量池
简单来说,HotSpot VM 里的字符串常量池(StringTable)是个哈希表,全局只有一份,被所有的类共享。
StringTable 具体存储的是 String 对象的引用,而不是 String 对象实例自身。String 对象实例在 JDK 6 及之前是在永久代里,从 JDK 7 开始放在堆里。
根据 Java 虚拟机规范的定义,堆是存储 Java 对象的地方,其他地方是不会有 Java 对象实体的,如果有的话,根据规范定义,这些地方也要算堆的一部分。
jdk1.8之后永久代的变化
永久代在 Java 8 被移除。根据官方提案的描述,移除的主要动机是:要将 JRockit 和 Hotspot 进行融合,而 JRockit 并没有永久代。
而据我们所了解的,还有另外一个重要原因是永久代本身也存在较多的问题,经常出现 OOM,还出过不少 bug。
根据官方提案的描述,永久代主要存储了三种数据:
1)Class metadata(类元数据),也就是方法区中包含的数据,除了编译生成的字节码被放在 native memory(本地内存)。
2)interned Strings,也就是字符串常量池中驻留引用的字符串对象,字符串常量池只驻留引用,而实际对象是在永久代中。
3)class static variables,类静态变量。
移除永久代后,interned Strings 和 class static variables 被移动了堆中,Class metadata 被移动到了后来的元空间。
为什么引入元空间?
在 Java 8 之前,Java 虚拟机使用永久代来存放类元信息,通过 - XX:PermSize、-XX:MaxPermSize 来控制这块内存的大小,随着动态类加载的情况越来越多,这块内存变得不太可控,到底设置多大合适是每个开发者要考虑的问题。
如果设置小了,容易出现内存溢出;如果设置大了,又有点浪费,尽管不会实质分配这么大的物理内存。
而元空间可以较好的解决内存设置多大的问题:当我们没有指定 -XX:MaxMetaspaceSize 时,元空间可以动态的调整使用的内存大小,以容纳不断增加的类。
元空间(metaspace)
元空间在 Java 8 移除永久代后被引入,用来代替永久代,本质和永久代类似,都是对方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存(native memory)。
元空间主要用于存储 Class metadata(类元数据),根据其命名其实也看得出来。
可以通过 -XX:MaxMetaspaceSize 参数来限制元空间的大小,如果没有设置该参数,则元空间默认限制为机器内存。