内存区域
程序计数器、虚拟机栈、本地方法栈这三个是线程私有的;堆、方法区是线程公有的;
程序计数器:记录线程走到字节码的哪一行;
虚拟机栈:由栈帧组成,每个栈帧包含局部变量表、操作数栈、动态链接、方法返回地址,当执行到一个方法的时候,就会把这个方法以栈帧形式压入栈
本地方法栈:与虚拟机栈差不多,只不过这个栈是给本地方法用的
堆:堆的垃圾回收算法常用的是分代回收法,所以堆被划分出新生代,老年代;
方法区: 1.7之前方法区的实现是永久代,会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。****1.8之后方法区的实现变成了元空间,字符串常量池和静态变量等移出到堆内存中,其余的(主要是类型信息)被移到了元空间中。
元空间和永久代的区别就在于永久代会受JVM的总空间大小的限制,而元空间受限制的是内存的总大小。
常量池:常量池中主要存放两类数据,一是字面量、二是符号引用。
字面量:比如String类型的字符串值或者定义为final类型的常量的值。
符号引用:
1.类或接口的全限定名(包括他的父类和所实现的接口)
2.变量或方法的名称
3.变量或方法的描述信息
4.this
当类的字节码被加载到内存中后,他的常量池信息就会集中放入到一块内存,这块内存就称为运行时常量池,并且把里面的符号地址变为真实地址。
垃圾回收
判断对象是否能够回收有两个办法:引用计数法和可达性分析
垃圾收集的算法:标记-清除、标记-整理、标记-复制、分代回收法
分代回收法
在内存中,分为新生代,老年代,永久代;这里的永久代也有叫方法区。新生代又分为Eden区,S0区,和S1区。一个对象创建,存储在Eden区,当Eden区满了,就会触发Minor GC,存活的对象将进入S0区,S0区满了之后会触发Minor GC,清空S0区内存,将存活的对象复制到S1区;S1满了也是GC清空到S0。倒来倒去,当次数达到16(可改)次时,会进入老年代;老年代满了会触发Full GC(会stop the world)。再满就会OOM了。
Full GC用的一般是标记整理和标记清除算法,所以不会转移,而Minor GC一般用的是标记-复制算法,所以会转移来转移去,同理,如果对象太大,会直接进老年代。
类加载过程
- 加载:
- 通过全类名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在内存中生成一个代表该类的
Class
对象,作为方法区这些数据的访问入口
- 验证:
验证文件格式、元数据、符号引用、字节码
- 准备:
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,会给类变量一个默认值,不对成员变量做内存分配。
从概念上讲,类变量所使用的内存都应当在方法区中进行分配。不过有一点需要注意的是:JDK 7 之前,HotSpot 使用永久代来实现方法区的时候,实现是完全符合这种逻辑概念的。 而在 JDK 7 及之后,HotSpot 已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这个时候类变量则会随着 Class 对象一起存放在 Java 堆中。
- 解析:
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。
- 初始化:
- 给静态变量赋值,给成员变量分配内存,赋值
类加载器
JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader
:
-
BootstrapClassLoader(启动类加载器) :最顶层的加载类,由 C++实现,负责加载
%JAVA_HOME%/lib
目录下的 jar 包和类或者被-Xbootclasspath
参数指定的路径中的所有类。 -
ExtensionClassLoader(扩展类加载器) :主要负责加载
%JRE_HOME%/lib/ext
目录下的 jar 包和类,或被java.ext.dirs
系统变量所指定的路径下的 jar 包。 - AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
双亲委派机制: 自底向上检查类是否被上层加载器加载,再从最顶向下尝试加载类
双亲委派机制的好处:
- 避免类的重复加载
- 保护Java核心API不被篡改