1、类加载:
双亲委派机制
加载一个类时要先加载加载器的父类。父类的加载器也会寻找父类的父类进行加载以此类推,直到找到自上层的父类。如果最上层的父类加载器无法加载(找不到)这个类。才会让子类去加载。如果最下层的子类也无法加载会抛出 CclassNotFoundException
常用的类加载器:
#AppClassLoader 加载的类路径 该加载器通常用来加载用户自定义的类
String appClassLoaderLoadPath=System.getProperties("java.class.path");
#ExtClassLoader 加载jar下拓展包的内容(/jre/lib/ext/xx.jar)
String extClassLoaderLoadPath=System.getProperties("java.ext.dirs");
#bootstrapClassLoader 加载加载jdk原生的jar包(/jre/lib/xx.jar)
2、jvm的结构
- 结构图
- 栈:
jvm内存空间中的一块,主管程序运行。生命周期和线程同步。是线程私有的一块内存空间。栈空间包含 方法内的局部变量表、操作数、方法出口信息、上一个方法和下一个方法的引用等其他等信息。
主要存储:八大基本类型+对象引用+实例方法
- 方法区:
方法区是被所有线程共享的,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也定义在此处,所以定义方法的信息也保存在此处。
包含:静态变量、常量、类信息(构造方法、接口定义)、运行时常量池。 - 本地方法栈:
用于运行本地方法的内存空间。 - 堆
堆在jvm中被所有线程共享。在虚拟机启动时创建,所有实例以及数组都在堆内存上分配空间,java堆可以处理在物理上不连续的空间。但是在逻辑上应该被视为连续的。
堆被分为永久代(8以后是元空间)+年轻代+老年代。内存调优主要是对堆内存进行调优。
永久代:称为非堆,方法区在此空间实现
年轻代: 分为Eden区和s区(from区+to区)
Eden区:对象刚创建就处于该空间内,当空间达到预警值会触发轻gc。存活的对象被移动到from区。
s区:分为相等的两块区域 from区和to区。Eden区存活的对象会先移动到from区。再次触发gc时,from区对象会移动到to区,并且计数加一,当计数达到最大值后(默认是15)如果依然存活会被移动至old区。
老年代
大对象、长时间存在的对象存储的区域。如果该区域剩余空间低于预警阀值会触发重gc(Full GC) 。重GC时会stop the word ! 因此要减少重GC的出现。
- 程序计数器
程序计数器是一个记录着当前线程所执行的字节码的行号指示器。
3、gc算法
- 引用计数法
给每一个对象一个引用计数器,当有一个地方引用他时,计数器加一,当有一个引用失效时计数器减一,当计数器值为0时,说明该对象不可达。应该被回收
优点:原理简单。且高效。
缺点:实现繁琐。每个对象要维护一个指针。指针会占用额外的空间。而且无法解决循环引用的问题。 - 可达性分析法
定义一个名为GC Roots的对象作为起点,这个GcRoots可以有多个,从这些节点开始,往下搜索,搜索所经过的路径称为引用链。当一个对象到GC Roots没有经过任何引用链时,则证明此对象是不可用的。可以被GC回收。
可以被作为gc roots的有:
1、虚拟机栈(栈桢中的本地变量表)中的引用的对象
2、方法区中的类静态属性引用的对象
3、方法区中的常量引用的对象
4、本地方法栈中JNI(Java Native Interface)的引用的对象
- 标记清除算法
先标记出存活的对象。然后清除掉没有标记的对象
缺点:要进行两次遍历。效率不高。而且会产生内存碎片。 - 复制算法
将空间分成相等的两份,所有新进来的对象只放在一边,gc时先遍历标记存活的对象。再把所有存活的对象在另一个空的空间复制一份。然后全部清空当前空间。
优点:不会产生内存碎片
缺点:总有一半空间是空着的。空间利用率不高。当大多数对象存活时间都比较长时,效率不高。 - 标记压缩(整理)算法
标记清除算法的优化方案。清除后将所有对象移动到一端。不会产生内存碎片。 - 复制-清除-压缩(整理)
标记压缩(整理)算法的优化方案。不会每次清除后都去压缩。清除一段时间后才回去压缩空间。 - 分代收集算法
针对堆内存不同的区域采用不同的算法。
年轻代使用复制算法
老年代使用标记压缩(整理)算法或者标记-清除-压缩算法
4、常用的垃圾处理器
- Serial收集器
单线程处理,在gc时,停止其他线程。通过 -XX:+UseParallelGC来启用该处理器。
优点:简单高效,拥有很高的单线程收集效率
缺点:收集过程需要暂停所有的线程。
算法:复制算法
适用范围:新生代
应用:client模式下默认的新生代收集器 - ParNew(并行)收集器
Serial的多线程版本
优点:多cpu时比Serial效率高
缺点:收集过程暂停所有应用程序线程,单cpu时效率比Serial差
适用范围:新生代
应用:service模式下虚拟机首选的收集器 - Parallel Scavenge收集器
和ParNew收集器类似,更关注系统吞吐量 - Serial Old收集器
Serial收集器的老版本。使用的标记整理算法,清除过程也和serial一样 - Parallel Old处理器
Parallel Scavenge 的老版本,使用多线程和标记整理算法进行垃圾回收。 - cms收集器
cms(concurrent mark sweep )收集器是一种以获取最短回收停顿时间
为目标的收集器
采用的是标记清除算法
整个过程分为四步:
(1) 初始标记 CMS initial Mark ----------> 标记gc roots 能够关联到的对象----------> stop the word (但是很快)
(2)并发标记 CMS concurrent Mark ---------> 进行GC Roots Tracing
(3)重新标记 CMS remark -------------------> 修改并发标记因用户程序变动的内容 ---->stop the word
(4) 并发清除 CMS concurrent sweep
在整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作。所以总体来说CMS收集器的内存回收过程是与用户的线程同步进行的。
优点:并发收集、低停顿
缺点:产生大量内存碎片、并发阶段会降低吞吐量。
- G1收集器
G1收集器在jdk7正式作为商用收集器
特点:
并行与并发
分代收集
空间整合,整体上属于“标记整理算法” 不会导致空间碎片
可预测的停顿,(与CMS相比,更先进的地方在于,能够让用户指定一个时间xxms,用于gc的时间不会超过xx毫秒)
使用G1处理器时,Java堆的内存布局,与其他收集器的差别很大,他将整个java堆划分成多个大小相等的独立区域。虽然还保留有新生代和老年代的概念,但是新生代和老年代不再是屋里隔离的了,他们都是一部分区域的集合。
回收步骤
初始标记(Initial Marking) 标记以下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程
并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划
拓展、内存溢出的原因
- 方法区溢出:
- 方法区溢出的原因:
- 方法区溢出解决方案
#错误信息
java.lang.OutOfMemoryError:metaspace
#解决方法
# 增大元空间内存
java -XX:MaxMetaspaceSize=3200m
# 方法区包括:静态变量常量、类信息(构造方法、接口定义)、运行时常量池。加载过多的类也会引起方法区溢出。方法区的变量不会被回收。
- 栈空间溢出
- 栈空间溢出的原因::
#错误信息
java.lang.StackOverflowError
#解决方法
# 增大元空间内存
java -XX:MaxMetaspaceSize=3200m
# 栈空间包括:方法内的局部变量表、操作数、动态链接、方法出口信息、其他等信息。
# 栈空间溢出原因:方法的递归调用陷入死循环。
- 堆空间溢出
- 溢出原因:
1、堆空间设置过小
2、高并发请求导致大量对象被创建。
3、内存泄露(?java什么时候会内存泄露) - 堆空间优化
1、提高堆内存
2、增加新生代对象最大寿命
3、增加yang区的内存
3、调整yang区和old区比例。默认是1:2
4、调整Eden区和s区比例 默认是8:1:1
5、在设置了-XX:MaxNewSize的情况下,-XX:NewRatio的值会被忽略
- 溢出原因:
#错误信息
java.lang.OutOfMemoryError:java heap space
#设置堆空间初始内存
-Xms256m
#设置堆空间最大内存
-Xmx512m
#增加新生代对象最大寿命
-XX:MaxTenuringThreshold(默认是15)
#指定年轻代初始内存和最大内存
-XX:NewSize100m -Xmn:200m或-XX:MaxNewSize200m
#设置新生代Eden区和s区比例默认是8:1:1 下面设置为 6:2:2
-XX:SurvivorRatio=6
#设置老年代和新生代的比例 设置方法同上
-XX:NewRatio
备注 jvm 常用参数
参数 | 含义 | 说明 |
---|---|---|
-XX:CICompilerCount=3 | 最大并行编译数 | 如果设置大于1,虽然编译速度会提高,但是同样影响系统稳定性,会增加JVM崩溃的可能 |
-XX:InitialHeapSize=100M | 初始化堆大小 | 简写-Xms100M |
-XX:MaxHeapSize=100M | 最大堆大小 | 简写-Xms100M |
-XX:NewSize=20M | 设置年轻代的大小 | |
-XX:MaxNewSize=50M | 年轻代最大大小 | |
-XX:OldSize=50M | 设置老年代大小 | |
-XX:MetaspaceSize=50M | 设置方法区大小 | |
-XX:MaxMetaspaceSize=50M | 方法区最大大小 | |
-XX:+UseParallelGC | 使用UseParallelGC | 新生代,吞吐量优先 |
-XX:+UseParallelOldGC | 使用UseParallelOldGC | 老年代,吞吐量优先 |
-XX:+UseConcMarkSweepGC | 使用CMS | 老年代,停顿时间优先 |
-XX:+UseG1GC | 使用G1GC | 新生代,老年代,停顿时间优先 |
-XX:NewRatio | 新老生代的比值 | 比如-XX:Ratio=4,则表示新生代:老年代=1:4,也就是新生代占整个堆内存的1/5 |
-XX:SurvivorRatio | 两个S区和Eden区的比值 | 比如-XX:SurvivorRatio=8,也就是(S0+S1):Eden=2:8,也就是一个S占整个新生代的1/10 |
-XX:+HeapDumpOnOutOfMemoryError | 启动堆内存溢出打印 | 当JVM堆内存发生溢出时,也就是OOM,自动生成dump文件 |
-XX:HeapDumpPath=heap.hprof | 指定堆内存溢出打印目录 | 表示在当前目录生成一个heap.hprof文件 |
XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps Xloggc:$CATALINA_HOME/logs/gc.log | 打印出GC日志 | 可以使用不同的垃圾收集器,对比查看GC情况 |
-Xss128k | 设置每个线程的堆栈大小 | 经验值是3000-5000最佳 |
-XX:MaxTenuringThreshold=6 | 提升年老代的最大临界值 | 默认值为 15 |
-XX:InitiatingHeapOccupancyPercent | 启动并发GC周期时堆内存使用占比 | G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示”一直执行GC循环”. 默认值为 45. |
-XX:G1HeapWastePercent | 允许的浪费堆空间的占比 | 默认是10%,如果并发标记可回收的空间小于10%,则不会触发MixedGC。 |
-XX:MaxGCPauseMillis=200ms | G1最大停顿时间 | 暂停时间不能太小,太小的话就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。 |
-XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数量 | 默认值随JVM运行的平台不同而不同 |
-XX:G1MixedGCLiveThresholdPercent=65 | 混合垃圾回收周期中要包括的旧区域设置占用率阈值 | 默认占用率为 65% |
-XX:G1MixedGCCountTarget=8 | 设置标记周期完成后,对存活数据上限为 G1MixedGCLIveThresholdPercent 的旧区域执行混合垃圾回收的目标次数 | 默认8次混合垃圾回收,混合回收的目标是要控制在此目标次数以内 |
-XX:G1OldCSetRegionThresholdPercent=1 | 描述Mixed GC时,Old Region被加入到CSet中 | 默认情况下,G1只把10%的Old Region加入到CSet中 |