1、JVM启动流程
java XXX -> 装载配置(根据当前路径和系统路径查找jvm.cfg) -> 根据配置查找JVM.dll(JVM主要实现) -> 初始化JVM获得JNIEnv接口(包括findClass等操作) -> 找到main方法执行2、JVM基本结构
2.1 PC寄存器
每个线程拥有一个PC寄存器,在线程创建时创建,它指向下一条指令的地址,执行本地方法时,PC寄存器的值为undefined。2.2 方法区
保存装载的类信息,类型的常量池,字段,方法字节码。通常和永久区(Perm)关联在一起。JDK6时,String等常量信息置于方法。JDK7时,已经移动到堆。
2.3 Java堆
应用系统对象都保存在Java堆中,所有线程共享Java堆,对分代GC来说,堆也是分代的,同时也是GC的主要工作区间。
|eden|s0|s1|tenured|
2.4 Java栈
线程私有,栈由一系列帧组成,帧保存一个方法的局部变量、操作数栈、常量池指针,每一次方法调用创建一个帧,并压栈。
2.5 内存模型
每一个线程有一个工作内存和主存独立,工作内存存放主存中变量的值的拷贝。
一个线程修改了变量,其他线程可以立即知道。保证可见性的方法:(1)volatile(2)synchronized (unlock之前,写变量值回主存)(3)final(一旦初始化完成,其他线程就可见)
2.7 有序性
在本线程内,操作都是有序的。在线程外观察,操作都是无序的(指令重排或主内存同步延时)。
3、JVM配置参数
3.1 打印GC信息打印GC简要信息:-verbose:gc 或者 -XX:+printGC
打印GC详细信息:-XX:+PrintGCDetails
打印CG发生的时间戳:-XX:+PrintGCTimeStamps
指定GC log的位置:-Xloggc:log/gc.log
每次一次GC后,都打印堆信息:-XX:+PrintHeapAtGC
3.2 监控类信息
-XX:+TraceClassLoading
-XX:+PrintClassHistogram
3.3 堆的分配参数
最大堆和最小堆:-Xmx20m -Xms5m
设置新生代大小:-Xmn
设置两个Survivor区和eden的比:-XX:SurvivorRatio
3.4 堆的推荐参数
官方推荐新生代占堆的3/8,幸存代占新生代的1/10。
3.5 永久区分配参数
永久区的初始空间和最大空间:-XX:PermSize -XX:MaxPermSize
4、GC 算法
4.1 引用计数法对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。
引用计数法的问题:(1)引用和去引用伴随加法和减法,影响性能(2)很难处理循环引用
4.2 标记清除
标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象(存活对象)。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
缺点:回收后的空间不连续,对于大对象的分配,工作效率低于连续空间。
4.3 标记压缩
标记-压缩算法适合用于存活对象较多的场合,如老年代。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。
标记-压缩算法是一种老年代的回收算法,这种方法避免了碎片的产生,又不需要两块相同的内存空间。通常使用在大部分对象都是存活对象。
4.4 复制算法
复制算法不适用于存活对象较多的场合 如老年代。将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
如果系统中的垃圾对象很多,复制算法复制存活对象的数量不会太大,复制算法的效率较高,同时回收后的内存空间没有碎片。Java的新生代串行垃圾回收器使用了复制算法,垃圾回收时,eden空间的存活对象被复制到未使用的survior的to空间中,正在使用的from空间中的年轻对象也会被复制到to空间中,或者老年对象直接进入老年代,eden和from空间剩余的都是垃圾对象,直接清除。
复制算法的最大问题是:空间浪费
4.5 分代思想
依据对象的存活周期进行分类,短命对象归为新生代,长命对象归为老年代。
根据不同代的特点,选取合适的收集算法:(1)少量对象存活,适合复制算法(2)大量对象存活,适合标记清理或者标记压缩。
由于新生代大多数新建对象很快被回收,因此选择效率较高的复制算法;老年代的对象都是经过几次垃圾回收之后幸存下来,生命周期较长,应常驻内存,采用标记-压缩算法提高垃圾回收效率。
5、YGC & FGC
新生代GC(YGC/MinorGC): 发生在新生代,Java对象大多生命短暂,YGC非常频繁,回收速度比较快。
老年代GC(FGC/MajorGC/FullGC): 发生在老年代,FGC的速度一般比YGC慢10倍以上。
新生代的垃圾回收器有新生代串行回收器、并行收集器、新生代并行回收器,老年代的垃圾回收器有CMS、老年代串行回收器、老年代并行回收器,它们之间的配对
新生代串行回收器-> CMS、老年代串行回收器,
并行收集器-> CMS、老年代串行回收器,
新生代并行回收器 ->老年代串行回收器、老年代并行回收器
5.1 新生代串行回收器
(1)单线程(2)Stop the world。独占式的垃圾回收,没有线程切换的开销,主要用于运行在client模式下的JVM。
5.2 并行收集器
Serial收集器的多线程版本,多线程、独占式回收,主要用于运行在Server模式下的虚拟机。
使用XX:+UseConcMarkSweepGC选项后的默认使用ParNew收集器,也可以通过XX:+UseParNewGC强制指定。
5.3 新生代并行回收器(Parallel Scavenge)
多线程、独占式回收,提供最大的吞吐量。
5.4 老年代串行回收器
使用标记压缩算法对老年代串行的、独占式的垃圾进行回收,应用程序很可能会停顿。
5.5 老年代并行回收器
使用多线程和标记压缩算法。注重吞吐量和CPU资源的场合,优先考虑使用新生代并行回收器 +老年代并行回收器收集器的组合
5.6 CMS(Concurrent Mark Sweep)
(1)并发(2)最短回收停顿时间为目标(3)标记-清除算法。
包括四个步骤(1)初始标记(2)并发标记(3)重新标记(4)并发清除(5)并发重置。
初始标记和重新标记是独占系统资源的,并发标记、并发清除、并发重置是和应用程序一起执行。
初始标记只是标记一下GC Roots能直接关联到的对象,重新标记是为了修正并发标记期间因为用户线程继续运行而导致标记产生变动的那一部分对象的标记记录,并发标记是进行根搜索算法。重置是初始化CMS的数据结构和数据。
5.7 GC触发的条件
(1)老年代内存超限 (2)新生代内存碎片 (3)永久代区域内存空间满 (4)System.gc()
6、类加载器
6.1 类的加载
类的加载是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。例如:在堆区存放描述Student类的Class对象,方法区存放Student的数据结构,Student类的Class对象提供了访问方法区内数据结构的接口。
6.2 类加载器种类
(1)JVM自带的类加载器
根类加载器(Bootstrap):加载JVM的核心类库java.lang.*;
扩展类加载器(Extension):父加载器为根类加载器,从jre\lib\ext下加载类库;
系统类加载器(System):应用加载器,父加载器为扩展类加载器,从classpath下加载类库;
(2)自定义类加载器:继承java.lang.ClassLoader;
6.3 类加载的父委托机制
在父委托机制中,除了JVM自带的根类加载器以外,其余的类加载器都有且只有一个父加载器,各个加载器按照父子关系形成了树形结构。
根类加载器 <—— 扩展类加载器 <—— 系统类加载器 <—— 自定义类加载器;
每个加载器都优先尝试用父类加载,若父类不能加载则自己尝试加载;若成功则返回Class对象给子类,若失败则告诉子类让子类自己加载,所有都失败则抛出异常。
7、OOM分析示例
$ jps
5104 Bootstrap
6724 Launcher
4476
8364 Jps
# 打印5104进程heap,每隔1000ms打印一次,一共打印10次
$ jstat -gccause 5104 1000 10
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC
0.00 59.86 85.49 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
0.00 59.86 85.51 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
0.00 59.86 86.14 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
0.00 59.86 86.71 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
0.00 59.86 86.73 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
0.00 59.86 86.74 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
0.00 59.86 86.74 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
0.00 59.86 86.75 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
0.00 59.86 86.80 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
0.00 59.86 86.83 65.95 98.15 95.80 51 4.823 6 2.804 7.627 Allocation Failure No GC
# dump文件,使用MAT分析
$ jmap -dump:format=b,file=test.bin 5104
Dumping heap to C:\Users\XXXXX\test.bin ...
Heap dump file created
# 或者jmap -F -dump:live,file=test.bin 5104
# 内存里面对象实例数与占用的情况
$ jmap -histo 5104 > jmap.his
num #instances #bytes class name
----------------------------------------------
1: 1015008 175762864 [C
2: 128099 109496392 [B
3: 54290 83146576 [I
4: 624147 24965880 com.xxx.dna.api.cache.AsnCache$Entry
5: 710537 17052888 java.lang.String
6: 89297 6097712 [Ljava.lang.Object;
7: 69135 6083880 java.lang.reflect.Method
8: 125741 4023712 java.util.HashMap$Node
9: 27895 3471936 [Ljava.util.HashMap$Node;
# 线程使用情况
$ jstack -l 5104 > jstack.jk
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.45-b02 mixed mode):
"Keep-Alive-Timer" #890 daemon prio=8 os_prio=1 tid=0x0000000064411800 nid=0x1a84 waiting on condition [0x000000006d83f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at sun.net.www.http.KeepAliveCache.run(KeepAliveCache.java:172)
at java.lang.Thread.run(Thread.java:745)