JVM的介绍

目录

一、JVM中对象在新生代和老年代的分配

二、JVM的堆栈分析及问题定位

1、查看JVM信息的一些监控工具

2、怎么根据JVM的参数进行调优

2.1 JVM调优的基础概念

2.2 什么是调优

2.3 JVM调优的一些案例

三、死亡对象判断方法

1、引用计数法

2、可达性分析算法

四、引用类型总结

1、强引用

2、软引用

3、弱引用

4、虚引用

五、垃圾收集算法

1、标记-清除算法

2、复制算法

3、标记-整理算法

4、分代收集算法

六、垃圾收集器

1、Serial收集器

2、ParNew收集器

3、Parallel Scavenge收集器

4、Serial Old收集器

5、Parallel Old收集器

6、CMS(Concurrent Mark Sweep)收集器

7、G1(Garbage-First)收集器

七、JVM的一些参数设计

1、内存溢出和内存泄漏

1.1 内存泄漏

1.2 内存溢出

2、关于JVM的参数

八、JVM中的类加载机制

1、关于类加载机制的介绍

2、类加载的分类及作用

3、双亲委派模型

3.1、双亲委派模型的工作过程 

3.2、使用双亲委派模型的好处

3.3、双亲委派模型的主要代码实现


一、JVM中对象在新生代和老年代的分配

JAVA中堆内存结构主要分为新生代内存(Young Generation)和老生代(Old Generation)以及元空间(MetaSpace)。

大多数情况下,对象在新生代中Eden区分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。如果对象在经历过第一次Minor GC后仍然能够存活,并且能够被Survivor容纳的话,将会被移动到Survivor空间(s0或者s1)中,并将对象年龄设为 1(Eden 区->Survivor 区后对象的初始年龄变为 1),如果不能被Survivor容纳的话,将会通过分配担保机制把新生代的对象提前转移到老年代中。

对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。也可以通过使用参数 -XX:MaxTenuringThreshold 来设置年龄的阈值。对于一些大对象会直接进入老年代。

空间分配担保是为了确保在Minor GC之前老年代本身还有容纳新生代所有对象的剩余空间。在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象空间,如果条件成立,这一次Minor GC可以确保是安全的。如果不成立,则虚拟机会先查看 -XX:HandlePromotionFailure 参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者 -XX: HandlePromotionFailure 设置不允许冒险,那这时就要改为进行一次 Full GC。

堆内存中的垃圾收集主要分为两种,部分收集(Partial GC)和整堆收集(Full GC):收集整个java堆和方法区。其中部分收集包括:1:新生代收集(Minor GC/Young GC),只对新生代进行垃圾收集;2:老年代收集(Major GC/Old GC),只对老年代进行垃圾收集。3:混合收集(Mixed GC),对整个新生代和部分老年代进行垃圾收集。

二、JVM的堆栈分析及问题定位

1、查看JVM信息的一些监控工具

 使用jps命令可以得到java进程列表,拿到java的进程id

每一个进程都会有一个JVM,我们使用jinfo 进程id,可以查询到该进程中jvm的各种参数,如果进程的参数设置不对,我们可以使用命令行,指定JVM的参数设置,然后启动jar程序,如下所示:

java -Xms1g -Xmx1g -jar common-mistakes-0.0.1-SNAPSHOT.jar

我们可以使用jvisualvm观察一下java程序,确认jvm的参数设置。在该界面中可以看到JVM的GC活动,可以看到堆内存的波动情况,活动的线程数。也可以在该图形界面进行手动GC操作。

如果希望看到各个内存区的 GC 曲线图,可以使用 jconsole 观察。jconsole 也是一个综合性图形界面监控工具,比 jvisualvm 更方便的一点是,可以用曲线的形式监控各种数据,包括 MBean 中的属性值。

如果没有条件使用图形界面(毕竟在 Linux 服务器上,我们主要使用命令行工具),又希望看到 GC 趋势的话,我们可以使用 jstat工具。jstat 工具允许以固定的监控频次输出 JVM 的各种监控指标,比如使用 -gcutil 输出 GC 和内存占用汇总信息,每隔 5 秒输出一次,输出 100 次,可以看到 Young GC 比较频繁,而 Full GC 基本 10 秒一次:如下图所示:

“其中,S0 表示 Survivor0 区占用百分比,S1 表示 Survivor1 区占用百分比,E 表示 Eden 区占用百分比,O 表示老年代占用百分比,M 表示元数据区占用百分比,YGC 表示年轻代回收次数,YGCT 表示年轻代回收耗时,FGC 表示老年代回收次数,FGCT 表示老年代回收耗时。”jstat 命令的参数众多,包含 -class、-compiler、-gc 等

通过命令行工具 jstack,也可以实现抓取线程栈的操作

最后,我们来看一下 Java HotSpot 虚拟机的 NMT 功能。通过 NMT,我们可以观察细粒度内存使用情况,设置 -XX:NativeMemoryTracking=summary/detail 可以开启 NMT 功能,开启后可以使用 jcmd 工具查看 NMT 数据。我们重新启动一次程序,这次加上 JVM 参数以 detail 方式开启 NMT:

-Xms1g -Xmx1g -XX:ThreadStackSize=256k -XX:NativeMemoryTracking=detail

在这里,我们还增加了 -XX:ThreadStackSize 参数,并将其值设置为 256k,也就是期望把线程栈设置为 256KB。我们通过 NMT 观察一下设置是否成功。启动程序后执行如下 jcmd 命令,以概要形式输出 NMT 结果。

2、怎么根据JVM的参数进行调优

JVM调优,设计到三个大的方面,在服务器出现问题之前要先根据业务场景选择合适的垃圾处理器,设置不同的虚拟机参数,运行中观察GC日志,分析性能,分析问题定位问题,虚拟机排错等内容,如果服务器挂掉了,要及时生成日志文件便于找到问题所在。

2.1 JVM调优的基础概念

目前的垃圾处理器中,一类是以吞吐量优先,一类是以响应时间优先:

吞吐量=运行代码时间/(用户代码执行时间+垃圾回收执行时间)

响应时间:STW(stop the world指的是GC事件发生过程中,会产生应用程序的停顿)越短,响应时间越好

所谓调优,首先确定追求什么,是吞吐量? 还是追求响应时间?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量,等等。

2.2 什么是调优

1. 根据需求进行JVM规划和预调优

2.优化运行JVM运行环境(慢,卡顿)

3.解决JVM运行过程中出现的各种问题(OOM,Out Of Menory 表示内存耗尽)

2.3 JVM调优的一些案例

系统CPU经常100%,如何调优?

CPU100%那么一定有线程在占用系统资源,我们可以使用接下来的步骤进行调优。

1.找出哪个进程cpu高(top)

2.该进程中的哪个线程cpu高(top -Hp)

3.导出该线程的堆栈 (jstack)

4.查找哪个方法(栈帧)消耗时间 (jstack)

5.工作线程占比高 | 垃圾回收线程占比高

CPU占用100%的原因有很多种,比如我们不小心创建了一个死循环,在死循环中不断地创建没用的对象,这个时候垃圾回收机制的工作压力较大,就会导致CPU的占用较大。(这里是垃圾回收)

还有一种可能是多个线程竞争CPU资源,或者线程切换也会发生这种情况。在一个功能的流程中如果有多个步骤都需要使用到多线程,那就会造成很多线程在竞争CPU资源,导致CPU飙升。这种情况,我们可以通过限制多线程等待队列的数量,从源头优先控制竞争队列大小。

系统内存飙高,如何查找问题?

1. 导出堆内存 (jmap)

2.分析 (jhat jvisualvm mat jprofiler ... )

这里一般是创建了一些较大的对象,而且垃圾回收器也不工作,这就导致了内存泄漏,造成了系统内存飙高。

三、死亡对象判断方法

1、引用计数法

引用计数法是给容器中的对象添加一个引用计数器,每当有一个地方引用它,计数器就加1,当引用失效,计数器减1,当一个对象的计数器为0时,就代表该对象是不可能再被使用的。缺点在于,该对象并不能解决对象之间循环引用的问题。

2、可达性分析算法

这个算法的基本思想就是通过一系列的成为“GC Roots”的对象作为起点,从这些节点开始向下搜索,节点走过的路径称为引用链,当一个GC Roots没有任何引用链相连的话,则证明此对象是不可用的需要被回收。

可以作为GC Roots的对象如下所示:

(1)虚拟机栈(栈帧中的本地变量表)中引用的对象

(2)本地方法栈(Naive方法)中引用的对象

(3)方法区中类静态属性引用的对象

(4)方法区中常量引用的变量

(5)所有被同步锁持有的对象

对于可达性分析法中不可达的对象,要真正回收该对象需要经历两次标记过程。第一次标记时会进行一次筛选,筛选此对象是否有必要执行finalize方法。被标记的对象会放到一个队列中进行二次标记,如果此时还是不可达对象,就会被回收。

四、引用类型总结

对于java中的引用类型,分为强引用、软引用、弱引用、虚引用(引用强度逐渐减弱)这四种引用类型。

1、强引用

强引用就是我们经常使用的引用,new一个对象使用的就是强引用。如果一个对象通过一串强引用链接可到达,他是不会被回收的。

2、软引用

软引用基本上和弱引用差不多,只是相比弱引用,它阻止垃圾回收期回收其指向的对象的能力强一些。如果一个对象是弱引用可到达,那么这个对象会被垃圾回收器接下来的回收周期销毁。但是如果是软引用可以到达,那么这个对象会停留在内存更时间上长一些。当内存不足时垃圾回收器才会回收这些软引用可到达的对象。

由于软引用可到达的对象比弱引用可达到的对象滞留内存时间会长一些,我们可以利用这个特性来做缓存。这样的话,你就可以节省了很多事情,垃圾回收器会关心当前哪种可到达类型以及内存的消耗程度来进行处理。

3、弱引用

Java中的弱引用具体指的是java.lang.ref.WeakReference<T>类,以下是官方文档对它做的说明:
弱引用对象的存在不会阻止它所指向的对象被垃圾回收器回收。弱引用最常见的用途是实现规范映射(canonicalizing mappings,比如哈希表)。
假设垃圾收集器在某个时间点决定一个对象是弱可达的(weakly reachable)(也就是说当前指向它的全都是弱引用),这时垃圾收集器会清除所有指向该对象的弱引用,然后把这个弱可达对象标记为可终结(finalizable)的,这样它随后就会被回收。与此同时或稍后,垃圾收集器会把那些刚清除的弱引用放入创建弱引用对象时所指定的引用队列(Reference Queue)中。

弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用WeakReference,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。

4、虚引用

与软引用,弱引用不同,虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。

当弱引用的指向对象变得弱引用可到达,该弱引用就会加入到引用队列。这一操作发生在对象析构或者垃圾回收真正发生之前。理论上,这个即将被回收的对象是可以在一个不符合规范的析构方法里面重新复活。但是这个弱引用会销毁。虚引用只有在其指向的对象从内存中移除掉之后才会加入到引用队列中。其get方法一直返回null就是为了阻止其指向的几乎被销毁的对象重新复活。

虚引用使用场景主要由两个。它允许你知道具体何时其引用的对象从内存中移除。而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大文件的情况。当你确定一个图片数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后再继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错误。

第二点,虚引用可以避免很多析构时的问题。finalize方法可以通过创建强引用指向快被销毁的对象来让这些对象重新复活。然而,一个重写了finalize方法的对象如果想要被回收掉,需要经历两个单独的垃圾收集周期。在第一个周期中,某个对象被标记为可回收,进而才能进行析构。但是因为在析构过程中仍有微弱的可能这个对象会重新复活。这种情况下,在这个对象真实销毁之前,垃圾回收器需要再次运行。因为析构可能并不是很及时,所以在调用对象的析构之前,需要经历数量不确定的垃圾收集周期。这就意味着在真正清理掉这个对象的时候可能发生很大的延迟。这就是为什么当大部分堆被标记成垃圾时还是会出现烦人的内存溢出错误。

使用虚引用,上述情况将引刃而解,当一个虚引用加入到引用队列时,你绝对没有办法得到一个销毁了的对象。因为这时候,对象已经从内存中销毁了。因为虚引用不能被用作让其指向的对象重生,所以其对象会在垃圾回收的第一个周期就将被清理掉。

五、垃圾收集算法

1、标记-清除算法

标记-清除算法分为标记和清除两个阶段,在标记阶段,首先利用死亡对象判断的方法标记出所有不需要回收的对象,在标记完成后在清除阶段统一回收掉所有没有被标记的对象。

该算法的缺点是会造成标记和清除两个过程效率不高,而且标记清除之后会产生大量不连续的内存碎片。

标记清除算法的流程是,首先当一个对象被创建时,给其一个标记位,假设为0(false),在标记阶段,将所有可达对象(或用户可以引用的对象)的标记位设置为1(true),扫描阶段清除所有标记为0的对象。

2、复制算法

为了解决标记-清除算法的效率和内存碎片问题,复制收集算法出现了,他将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块,然后再把使用的空间一次清理掉。这样就能使每次的内存回收都是对内存区间的一半进行回收。

复制算法虽然改进了标记-清除算法,但是仍然存在一些问题。首先是可用内存变小,可用内存缩小为原来的一半。然后就是该算法不适合老年代,如果存活对象数量较大,复制性能会变得很差。

3、标记-整理算法

标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

该算法由于多了整理这一步,所以算法的效率也不高,但是因为使用的是整块内存,所以可以用到老年代这种垃圾回收频率不是很高的场景。

4、分代收集算法

该算法没有什么新的思想,知识根据对象存活周期的不同将内存分为几块,根据各个年代的特点选择合适的垃圾收集算法。

六、垃圾收集器

1、Serial收集器

这是一个单线程收集器,意外这它只会使用一个CPU或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其他所有的工作线程直到收集结束。

在新生代使用时用的是标记-复制算法,在老年代使用时用的是标记-整理算法。

2、ParNew收集器

ParNew收集器是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。

在新生代使用时用的是标记-复制算法,在老年代使用时用的是标记-整理算法。

3、Parallel Scavenge收集器

Parallel Scavenge也是使用标记-复制算法的多线程收集器,他相对于ParNew的特别之处在于Parallel Scavenge 收集器的目的是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。

作为一个吞吐量优先的收集器,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整停顿时间。这就是 GC 的自适应调整策略(GC Ergonomics)。

在新生代使用时用的是标记-复制算法,在老年代使用时用的是标记-整理算法。

JDK1.8默认使用的时Parallel Scavenge+Parallel Old,如果指定了-XX:+UseParallelGC 参数,则默认指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 来禁用该功能。

4、Serial Old收集器

Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。

5、Parallel Old收集器

Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。

6、CMS(Concurrent Mark Sweep)收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。它是HotSpot虚拟机第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程同时工作。CMS收集器是基于标记-清除算法实现的,它的运行过程主要如下所示:

(1)初始标记:暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;

(2)并发标记:同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。

(3)重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短

(4)并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

CMS收集器可以实现并发收集并且低停顿的特点,但是他也会出现对CPU资源敏感、无法处理浮动垃圾、使用的“标记-清除算法”导致收集结束时大量空间碎片产生的缺点。

7、G1(Garbage-First)收集器

G1 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。

G1垃圾收集器具有下面几个特点:

(1)并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。

(2)分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。

(3)空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。

(4)可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。

G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

G1垃圾收集器的详细介绍

Garbage First(G1)收集器开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。在G1收集器出现之前的所有其他收集器,垃圾收集的目标范围要么是整个新生代,要么就是整个老年代,再要么就是整个Java堆。而G1跳出了这个限制,它可以面向堆内存任何部分来组成回收集进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。

G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。 此外,还有一类专门用来存储大对象的特殊区域(Humongous Region)。G1认为只要超过了Region一半的对象即可判定为大对象。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待。

更具体的处理思路是,让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。

G1收集器的运作过程大致可划分为以下四个步骤:初始标记、并发标记、最终标记、筛选回收。其中,初始标记和最终标记阶段仍然需要停顿所有的线程,但是耗时很短。 

(1)初始标记(initial mark),标记了从GC Root开始直接关联可达的对象。STW(Stop the World)执行,停止所有的应用线程。

(2)并发标记(concurrent marking),和用户线程并发执行,从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象

(3)最终标记(Remark),STW,标记再并发标记过程中产生的垃圾。

(4)筛选回收(Live Data Counting And Evacuation),制定回收计划,选择多个Region 构成回收集,把回收集中Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。需要STW。

G1与CMS的对比: G1从整体来看是基于标记整理算法实现的收集器,但从局部上看又是基于标记复制算法实现。无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,垃圾收集完成之后能提供规整的可用内存。

比起CMS,G1的弱项也可以列举出不少。例如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS要高。 G1与CMS的选择: 目前在小内存应用上CMS的表现大概率仍然要会优于G1,而在大内存应用上G1则大多能发挥其优势,这个优劣势的Java堆容量平衡点通常在6GB至8GB之间。

七、JVM的一些参数设计

1、内存溢出和内存泄漏

内存溢出试着程序在申请内存时,没有足够的内存空间供其使用,出现out of memory,比如申请一个integer,但给他long才能存下的数,那就是内存溢出。

内存泄漏是指你向系统申请分配内存进行使用(new),但是使用完后却不归还(delete),结果申请到的那块内存自己也不能访问,而系统也不能再次将它分配给需要的程序,就像是有一部分内存遗漏了,这就是内存泄漏。简单来说就是不在会被使用的对象不能被回收,这就是内存泄漏。

1.1 内存泄漏

下面是内存泄漏的一个示例代码,如图所示,我们只希望object对象在method1()方法运行的时候运行,在其他地方不会用到object,但是因为object是Simple类的一个属性。所以,只有当Simple类创建的对象被释放侯,object才会被释放,这就是一种内存泄漏。

public class Simple {
    Object object;
    public void method1(){
        object = new Object();
    //...其他代码
    }
}

1.2 内存溢出

导致内存溢出的原因由很多种,主要有接下来几个原因。

(1)内存种加载的数据量太过于庞大,如一次从数据库种取出过多数据,当从一个较大的数据库种使用select *语句就会造成这样的现象。

(2)集合类种有对对象的引用,使用完后未清空,使得JVM不能回收。

(3)代码中存在死循环或者循环产生过多重复的对象实体。

(4)使用的第三方软件的BUG

(5)启动参数内存值设定的过小

2、关于JVM的参数

jvm调优,调的是稳定,并不能带给你性能的大幅提升。服务稳定的重要性就不用多说了,保证服务的稳定,gc(垃圾回收)永远会是Java程序员需要考虑的不稳定因素之一。复杂和高并发下的服务,必须保证每次gc不会出现性能下降,各种性能指标不会出现波动,gc回收规律而且干净,找到合适的jvm设置。

-Xms:初始堆⼤⼩

-Xmx:最⼤堆⼤⼩

-XX:NewSize=n:设置年轻代⼤⼩

-XX:NewRatio=n:设置年轻代和年⽼代的⽐值。如:为3,表示年轻代与年⽼代⽐值为 1:3,年轻代占整个年轻代 年⽼代和的 1/4

-XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的⽐值。注意 Survivor 区有两个。如:3,表示 Eden:Survivor=3:2,⼀个Survivor区占整个年轻代的 1/5

-XX:MaxPermSize=n:设置持久代⼤⼩

八、JVM中的类加载机制

1、关于类加载机制的介绍

虚拟机把描述类的数据从 Class ⽂件加载到内存,并对数据进⾏校验、转换解析和初始化,最终形成可以被虚拟机 直接使⽤的 Java 类型,这就是虚拟机的类加载机制。 类从被加载到虚拟机内存中开始,到卸载出内存为⽌,它的整个⽣命周期包括:加载、验证、准备、解析、初始化、使⽤、卸载 7 个阶段。其中验证、准备、解析 3 个部分统称为连接,这7个阶段发⽣的顺序如下图所示:

2、类加载的分类及作用

(1)启动类加载器(Bootstrap ClassLoader):这个类加载器是由 C++ 语⾔实现的,是虚拟机⾃身的⼀部分。负责将存在 \lib ⽬录中的,或者被 -Xbootclasspath 参数所指定的路径中的类库加载到虚拟机内存 中。启动内加载器⽆法被 Java 程序直接引⽤,⽤户在编写⾃定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使⽤null即可;

(2)扩展类加载器(Extension ClassLoader):这个类加载器sun.misc.Launcher$ExtClassLoader 实现,它负责加载\lib\ext⽬录中的,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使⽤扩展类加载器。

(3)应⽤程序类加载器 (Application ClassLoader):这个类加载器由 sun.misc.Launcher$AppClassLoder 实 现。由于个类加载器是ClassLoader 中的 getSystemClassLoader() ⽅法的返回值,所以⼀般也称之为系统类加载器。它负责加载⽤户路径(ClassPath)所指定的类库,开发者可以直接使⽤这个类加载器,如果应⽤程序中没有⾃定义过⾃⼰的类加载器,⼀般情况下这个就是程序中默认的类加载器。

3、双亲委派模型

关于上面的三个类加载器的继承结构如下所示:

3.1、双亲委派模型的工作过程 

如果⼀个类加载器收到了类加载请求,它⾸先不会⾃⼰去尝试加载这个类,⽽是把这个请求委派给⽗类加载器去完 成。每⼀个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当⽗类 加载器反馈⾃⼰⽆法完成这个加载请求(它的搜索范围中没有找到所需的类)时,⼦加载器才会尝试⾃⼰去加载。

3.2、使用双亲委派模型的好处

Java 类随着它的类加载器⼀起具备了⼀种带有优先级的层次关系。例如:类 java.lang.Object,它存放在 rt.jar 中,⽆论哪⼀个类加载器需要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进⾏加载,因此 Object 类在程序的各种类加载器环境中都是同⼀个类(使⽤的是同⼀个类加载器加载的)。相反,如果没有使⽤双亲委派 模型,由各个类加载器⾃⾏去加载的话,就会出现重复加载同一个类,加入双亲委派模型之后,就可以从顶层到下面问一问,如果加载过了就不用再加载一遍了,保证数据安全。如果⽤户⾃⼰编写了⼀个 java.lang.Object 类,并放在程序的 ClassPath中,那么系统将会出现多个不同的 Object 类,Java 类型体系中最基础的⾏为也就⽆法保证,应⽤程序也将变得⼀⽚混乱。

3.3、双亲委派模型的主要代码实现

实现双亲委派的代码都集中在 java.lang.ClassLoader 的 loadClass() ⽅法中,逻辑清晰易懂:先检查是否已经被加载过,若没有加载则调⽤⽗加载器的 loadClass() ⽅法,若⽗加载器为空则默认使⽤启动类加载器作为⽗类加载器。如果⽗类加载失败,抛出 ClassNotFoundException 异常后,再调⽤⾃⼰的 findClass() ⽅法进⾏加载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值