一、运行时数据区
1. 方法区
方法区:jdk1.8之前被称为永久代,1.8后被元空间代替
方法区主要用于存储类的结构信息如:字段、方法、常量池、静态变量等
1.1 永久代和元空间
二者都是Java中用于存储类信息的区域,区别:
存储空间:永久代可以理解为Java堆中的一块区域,元空间是直接在本地内存中分配的区域
GC方式:永久代回收基于标记-清除算法,元空间使用G1和CMS算法
内存大小:永久代大小是在JVM启动时确定的,元空间是根据本地内存动态的进行分配,但当本地内存被耗尽时仍会出现溢出,虚拟机抛出OutOfMemoryError:Metaspace异常
类型信息存储方式:永久代主要存储类型信息和方法信息等元数据,元空间则是将这些元素保存在本地内存中方便大量信息的处理
2. 堆
JVM管理最大的一块区域,用于存储对象的实例和数组
堆是线程共享的,分为新生代、老年代等,也称之为GC堆
3. 虚拟机栈
Java方法执行的内存模型,每个方法执行的时候都会创建栈帧,用于存储局部变量、操作数栈、动态链接、方法出入口信息等
栈帧随着方法的调用进行出栈操作
4. 本地方法栈
用于执行本地方法(非Java方法)
目的:完成java与外部程序的交互
5. 程序计数器
记录当前线程执行地址,线程私有的每一个线程都有一个程序计数器
(类似于闯关游戏的存档,保存当前程序执行的到的进度)
6. 运行时常量池
方法区的一部分,用于存放编译器生成的各种字面量和符号引用
7. 直接内存
通过native函数库直接分配堆外内存,直接内存不受java堆大小的限制,过于频繁地使用直接内存可能会导致本地内存耗尽,导致系统崩溃
二、类加载器
将java类加载都内存中,根据类的全限定名找到对应的字节码,并把其加载到jvm中进行解析、链接、初始化。
类加载器遵循委托机制(Delegation Model),即当一个类加载器接收到类加载请求时,会将这个请求让自己的父类加载器进行加载,如果父类加载器也存在父类加载器,则继续向上委托,直到达到最上层的类加载器停止,如果父类加载器可以完成类加载任务,则成功返回,如果
不能则让子类加载器尝试加载
优点:防止类被重复加载,在类加载时有明确的优先级关系,所以当父类加载成功后子类没必要重新加载
缺点:阻止了自定义类加载器的更新,会首先委派给父类;
1. 启动类加载器
它是JVM的一部分,负责加载Java核心类,如java.lang包中的类。它是由C++编写的,无法直接在Java代码中访问
2. 扩展类加载器
它负责加载JRE扩展目录(jre/lib/ext)下的JAR包中的类。可以通过Java代码获取到该类加载器的实例
3. 应用程序类加载器
也称为系统类加载器,它负责加载应用程序类路径(Classpath)上指定的类。通常情况下,Java开发者编写的代码都由该类加载器加载
4. 自定义类加载器
通过编写自定义的类加载器来实现特殊的加载需求,比如加载网络上的类、动态生成类等
三、垃圾回收
垃圾回收:GC;主要用于java的堆管理
堆:java管理的最大的一块内存空间,用于存放各种类的实例对象
1. 什么是垃圾回收
将java虚拟机中无用的对象清除,释放出占用的内存空间
GC是不定时的,是根据堆中对象占用空间大小、其他的特定条件触发。我们可以通过System.gc方法建议jvm执行垃圾回收
2. 新生代、老年代、永久代(方法区)区别
- 老年代只有一个区域,新生代分为三个区域:Eden、From Survivor、To Survivor。新生代,老年代共同组成堆。堆是jvm管理的最大的内存空间,主要是用来存放各种类的实例对象。
- 默认新生代、老年代比例为1:2,新生代占用1/3堆的大小,老年代占用2/3堆的大小
- 新生代中 Eden、From Survivor、To Survivor的默认比例为 8:1:1可以通过参数 –XX:SurvivorRatio 来设定
- jvm每一次都只会使用Eden和一块Survivor,总是有一块Survivor是空闲的
- 永久代就是jvm的方法区,此区域中的数据比老年代和新生代中的数据更难回收
3. 分代的目的
根据不同的分代,选取最适合的垃圾回收算法,提高GC效率
- 新生代:每一次垃圾回收都会有大量的对象被GC,所以采用复制算法,只需要少量的复制成本就可以完成
- 老年代:老年代中对象存活率较高,采用复制算法话费的空间较多,采用标记整理、标记清楚算法
数据大部分情况都会被分配到老年代当中,除非当前数据再创建时需要分配的内存过大
当Eden区域没有空闲空间时jvm会触发MinorGc,当对象经过一次MinorGc还存活并且可以被Survivor接受那么会被移动到Survivor区域当中,此时当前对象年龄为1。每经过一次MinorGc,Survivor区域中的对象年龄都会加一,当年龄达到一定程度时(默认值为15),会被转移到老年代中
4. MinorGc、MajorGc、FullGc区别
- MinorGc:新生代Gc,指发生在新生代的垃圾回收动作。由于新生代中java对象生命周期较短,所以MinorGc会频繁执行
- MajorGc:老年代Gc,指发生在老年代的垃圾回收动作。通常MajorGc会连带着MinorGc一起执行,且比MinorGc慢得多
- FullGc:清理整个对空间,包括新生代、老年代
5. MinorGc触发条件
- 当Eden区域满了,或者新建对象时发现Eden区域不够用了,触发MinorGc
6. MajorGc、FullGc触发条件
- 永久代空间不足
- 有对象晋升老年代,但是老年代空间不足以存放
- 堆内存分配对象过大
- CMS GC异常
- 执行了System.gc
7. 对象存活判断
引用计数法、可达性分析法
7.1 引用计数法
创建对象时,同时给对象创建一个计数器。当有引用指向对象时,计数器加一;当对象的引用被删除时,计数器减一
- 优点:简单、特定情况下效率高
- 缺点:很难解决对象之间的相互引用
7.2 可达性分析法
从GC Roots开始向下搜索,搜所走过的路径为引用链。当一个对象到GCRoots没有引用时,则表明这个对象可以回收,是不可用的
解决了对象之间循环引用问题
8. 垃圾回收算法
8.1 标记-清除算法
为每一个对象存储一个标记位,记录对象状态(存活/死亡)
第一阶段为标记阶段,检查对象是否死亡;第二个阶段为清理阶段,该阶段对死亡对象进行清理
- 优点:可以解决循环引用问题;必要时才会进行回收
- 缺点:回收时应用会挂起;标记清除的效率不高,尤其是当扫描对象比较多的时候;会造成内存碎片,虽然有空间但是连续性的空间不足以存储一个大的对象
- 应用场景:一般应用于老年代中,因为老年代中的对象生命周期长、对象占用空间大,所以使用标记清除算法
8.2 标记-整理算法
为每一个对象存储一个标记位,记录对象状态(存活/死亡)
第一阶段为标记阶段,检查对象是否死亡;第二个阶段为整理阶段,将存活的数据整理移动到另一个存储空间中,之后将剩下的对象全部清除
- 优点:解决了内存碎片问题
- 缺点:因为更新了存活对象地址,所以需要更新引用
- 应用场景:老年代中
8.3 复制算法
将内存划分为两块区域,对象只占用一块区域进行存储。当需要进行GC时,将存储对象的内存中存活的对象复制到另一块空闲的区域中,之后对原存储对象的内存清空。后续一直这么循环处理
- 优点:解决了内存碎片、对象引用更新问题
- 缺点:会造成一部分的内存空间浪费,可以根据实际情况对两个内存块比例进行调整;当存活对象数目较大时,复制性能算法性能会较差
- 应用场景:一般应用于新生代中,因为新生代中的对象生命周期较短,复制时对象数据量不会太高;新生代中被分为三块区域->Eden、From Survivor、To Survivor,复制算法就在Eden->From 与 To之间进行;jvm在进行两块内存空间划分时不是按照1:1进行划分,一般是8:1:1,所以始终有90%的区域来对对象进行处理
9. 垃圾收集器
四、参数配置
JVM内存参数简述
#常用的设置
-Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。
-Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
-Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。
-XX:NewSize=n 设置年轻代初始化大小大小
-XX:MaxNewSize=n 设置年轻代最大值
-XX:NewRatio=n 设置年轻代和年老代的比值。如: -XX:NewRatio=3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代+年老代和的 1/4
-XX:SurvivorRatio=n 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。8表示两个Survivor :eden=2:8 ,即一个Survivor占年轻代的1/10,默认就为8
-Xss:设置每个线程的堆栈大小。JDK5后每个线程 Java 栈大小为 1M,以前每个线程堆栈大小为 256K。
-XX:ThreadStackSize=n 线程堆栈大小
-XX:PermSize=n 设置持久代初始值
-XX:MaxPermSize=n 设置持久代大小
-XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。
#下面是一些不常用的
-XX:LargePageSizeInBytes=n 设置堆内存的内存页大小
-XX:+UseFastAccessorMethods 优化原始类型的getter方法性能
-XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc(),默认启用
-XX:+AggressiveOpts 是否启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等,jdk6纸之后默认启动
-XX:+UseBiasedLocking 是否启用偏向锁,JDK6默认启用
-Xnoclassgc 是否禁用垃圾回收
-XX:+UseThreadPriorities 使用本地线程的优先级,默认启用
JVM的GC收集器设置
-XX:+UseSerialGC:设置串行收集器,年轻带收集器
-XX:+UseParNewGC:设置年轻代为并行收集。可与 CMS 收集同时使用。JDK5.0 以上,JVM 会根据系统配置自行设置,所以无需再设置此值。
-XX:+UseParallelGC:设置并行收集器,目标是目标是达到可控制的吞吐量
-XX:+UseParallelOldGC:设置并行年老代收集器,JDK6.0 支持对年老代并行收集。
-XX:+UseConcMarkSweepGC:设置年老代并发收集器
-XX:+UseG1GC:设置 G1 收集器,JDK1.9默认垃圾收集器