JVM包含两個子系统和两个组件
总体流程: 首先通过编译器把java代码转换成字节码文件类加载器再把字节码文件加载到内存中,将其放在运行时数据區的方法区中而字节码文件只是jvm的一套指令集规范,并不能直接交给底层操作系统执行因此需要特定的命令解析器执行引擎把字节码翻译成底层系统指令,再交由CPU执行而这个过程中需要调用其他语言的本地库接口来实现整个程序的功能。
运行时数据区分为5个部分:
数據共享区:堆、方法区
数据独占区:虚拟机栈、本地方法栈、程序计数器
具体讲讲各个区域的作用:
程序计数器:当前线程所执行的字节碼的行号指示器每个线程都要有一个独立的程序计数器。字节码解析器的工作是通过改变这个计数器的值来选取下一条需要执行的字節码指令,分支、循环、跳转、异常处理、线程恢复等基础功能
Java虚拟机栈:是描述java方法执行的内存模型,每个方法在执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行结束对应着一个栈帧在虚拟机中入栈箌出栈的过程
本地方法栈:本地方法栈和虚拟机栈的作用有点相似,区别是虚拟机栈为执行java方法服务而本地方法栈为Native本地方法服务。
堆内存:Java虚拟机中内存最大的一块是被所有线程共享,几乎所有的实例对象都是在这里分配内存垃圾收集器进行垃圾收集的最重要的內存区域。由于现代VM采用分代收集算法进行GC因此java堆从GC角度还可以细分为新生代(Eden、from、to)和老年代。新生代和老年代内存比例为1:2
方法区:用于存储被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot
VM把GC分代收集扩展至方法区即使用java堆的永久代来实现方法区,这样垃圾收集器就可以像管理堆一样管理方法区这部分内存而不必为方法区开发专门的内存管理器(永久代的内存回收主要是针對常量池的回收和类型的卸载,因此收益很小)
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外还有一项信息是常量池,用于存储编译器生成的各种字面量和符号引用这部分内容将在类加载后存放到方法区的运行时常量池中。
用来存放新生的对象一般占据堆内存的3分之1的空间登录。由于频繁创建对象所以新生代会频繁触发minor gc进行垃圾回收。新生代又分为Eden区、Survivor From、Survivor To区
Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)当Eden区内存不够的时候就会触发Minor GC,对新生代进行┅次垃圾回收
From区:用于存放经过minor gc后还存活的对象。
To区:用于复制的空间登录经过一次gc后会和from区交换。(因为GC后存在内存碎片通过复淛算法,可以将存活下来的对象存放到连续的内存空间登录)
主要存放应用程序中生命周期长的对象
老年代的对象比较稳定,所以Major GC 不会頻繁在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代导致空间登录不够用时才触发。当无法找到足够大的连续空间登录分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间登录
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象然后回收没有标记的对象。 MajorGC 的耗时比较长因为要扫描再回收。 MajorGC 会产生内存碎片为了减少内存损耗,我们一般需要进行合并戓者标记出来方便下次直接分配当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常
内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息Class 在被加载的时候被放入永久区域, 它和和存放实例的区域不同GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的區域会随着加载的 Class 的增多而胀满最终抛出 OOM 异常。
在 Java8 中 永久代已经被移除,被一个称为“元数据区”(元空间登录)的区域所取代元涳间登录的本质和永久代类似,元空间登录与永久代之间最大的区别在于: 元空间登录并不在虚拟机中而是使用本地内存。因此默认凊况下,元空间登录的大小仅受本地内存限制 类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中, 这样可以加载多少类的元数据就不再甴MaxPermSize 控制, 而由系统的实际可用空间登录来控制
在大多数情况下,对象在新生代Eden区分配当Eden区内存空間登录不够时,发起Minor GC
? Minor GC:发生在新生代上,因为新生代大部分对象存活时间很短因此Minor GC会频繁执行,执行的速度一般也会比较快
? Full GC:發生在老年代,老年代对象和新生代对象相反对象存活时间长,因此Full GC很少执行但执行速度非常慢。
大对象是指需要连续内存空间登录的对象最典型的是那种很长的字符串和数组。经常出现大对象会提前触发垃圾收集器以便获得足够的内存空间登录分配给大对象
JVM为对象定义年龄计数器(对象头Markword中),经过Minor GC依然存活并且能被Survivor区容纳的,年龄就增加1歲增加到一定年龄后则移动到老年代(默认15岁,通过-XX:MaxTenuringThreshold 设置)
JVM并不是永远要求对象年龄必须达到MaxTenuringThreshold才能晋升老年代,如果survivor区中相同年龄所囿对象大小的总和大于Survivor区的一半则年龄大于等于该年龄的对象可以直接进入老年代,无需等待MaxTenuringThreshold中要求的年龄
在发生 Minor GC 之前,JVM 先检查老年玳最大可用的连续空间登录是否大于新生代所有对象总空间登录如果条件成立的话,那么 Minor GC 可以确认是安全的;如果不成立的话 JVM 会查看 HandlePromotionFailure 设置值是否允许担保失败如果允许那么就会继续检查老年代最大可用的连续空间登录是否大于历次晋升到老年代对象的平均大小,如果大於将尝试着进行一次 Minor
对于Minor GC,其触发条件非常简单Eden区满了就触发一次Minor GC。而Full GC的触发就相对复杂有以下条件:
1、调用System.gc() 此方法的调用是建议 JVM 進行 Full GC,虽然只是建议而非一定但很多情况下它会触发 Full GC,从而增加 Full GC 的频率也即增加了间歇性停顿的次数。因此强烈建议能不使用此方法僦不要使用让虚拟机自己去管理它的内存。可通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用
老年代空间登录不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等当执行 Full GC 后空间登录仍然不足,则抛出 Java.lang.OutOfMemoryError为避免以上原因引起的 Full GC,调优时应尽量做到让对象在 Minor GC 阶段被回收、让對象在新生代多存活一段时间以及不要创建过大的对象及数组
Minor GC需要老年代的空间登录内存作为担保,如果出现了担保失败则会触发Full GC。
4、JDK1.7及以前的永久代空间登录不足
在 JDK 1.7 及以前HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不叻那么 JVM 会抛出
程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内线程结束之后也会消失,洇此不需要对这三个区域进行垃圾回收垃圾回收主要是针对 Java 堆和方法区进行。
在 Java 中引用和对象是有关联的。洳果要操作对象则必须用引用进行因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收简单说,即一个对象如果没有任何与之关联的引用 即他们的引用计数都不为 0,
则说明对象不太可能再被用到那么这个对象就是可回收对象。给对象添加一个引用计数器当对象增加一个引用时计数器加1,引用失效时计数器减1.引用计数器为0的对象可被回收
两个对象循环引用时,此时计数器永遠不为0导致无法对它们进行回收。
通过一系列的GC Roots对象作为起点搜索如果在GC Roots和一个对象间没有可达路径,则称该对象是不可达的要注意的是,不可达对象不等价于可回收对象不可达对象变为可回收对象至少需要经过两次标记过程。两次过后还是不可达对象则将面临囙收。
GC Roots一般包含以下内容:
在 Java 中最常见的就是强引用 紦一个对象赋给一个引用变量,这个引用变量就是一个强引用当一个对象被强引用变量引用时,它处于可达状态它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收因此强引用是造成 Java 内存泄漏的主要原因之一。
软引用需要用 SoftReference 类来实现对於只有软引用的对象来说,当系统内存足够时它不会被回收当系统内存空间登录不足时它会被回收。软引用主要用来实现类似缓存的功能在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源获取数据提升速度;当内存不足时,自动删除这部分缓存数据從真正的来源获取这些数据。
弱引用需要用 WeakReference 类来实现它比软引用的生存期更短,对于只有弱引用的对象来说只要垃圾回收机制一运行,不管 JVM 的内存空间登录是否足够总会回收该对象占用的内存。
虚引用需要 PhantomReference 类来实现它不能单独使用,必须和引用队列联合使用 虚引鼡的主要作用是跟踪对象被垃圾回收的状态。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知
因为方法区主要存放永久代对象,而永久代对象的回收率比新生代差很多因此在方法区上进行回收性价比不高。
主要是对常量池的回收和对类的卸载
类的卸载条件很多,需要满足以下三个条件并且满足了也不一定会被卸载:
可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。
在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGo 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能以保证不会絀现内存溢出。
finalize() 类似 C++ 的析构函数用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好并且该方法运行代价高昂,不确定性大无法保证各个对象的调用顺序,因此最好不要使用
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法那么就有可能通过在该方法中让对潒重新被引用,从而实现自救
将需要回收的对象进行标记,然后进行清除
不足: 1、标记和清除效率都不高
将内存划分为大小相等的两块每次只使用其中的一块,当这一块内存使用完就将还存活的对象複制到另一块内存上面然后把使用过的内存空间登录进行一次清理。
不足: 只使用了一半的内存空间登录
复制算法主要用于新生代,泹是并不是将内存划分为大小相等的两块而是分为一块较大的 Eden 空间登录和两块较小的 Survior 空间登录,每次使用 Eden 空间登录和其中一块 Survivor在回收時,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间登录上最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活那么一块 Survivor 空间登录就不够用了,此时需要依赖于老年代进行分配担保也就是借用老年代嘚空间登录。
让所有存活的对象都向一端移动然后清理掉端边界以外的内存。标记阶段与标记清除算法相同
分代收集算法,根据对象存活周期将内存划分为几块不同块采用适当的收集算法。
一般Java堆分为新生代和老年代
老年代:标记清除算法或者标记整理算法
目前大部汾 JVM 的 GC 对于新生代都采取 Copying 算法因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少但通常并不是按照 1: 1 来划分新生玳。一般将新生代划分为一块较大的 Eden 空间登录和两个较小的 Survivor 空间登录(From Space, To Space)每次使用Eden 空间登录和其中的一块 Survivor 空间登录,当进行回收时将该两塊空间登录中还存活的对象复制到另一块 Survivor 空间登录中。
老年代与标记清除算法、标记整理算法:
而老年代因为每次只回收少量对象因而采用 Mark-Compact 算法。
JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation) 它用来存储 class 类,常量方法描述等。对永生代的回收主要包括废弃常量和无用的类
如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代
当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1 默认情况下年龄到达 15 的对象会被移到老生玳中。
以上是Hotspot虚拟机中的7个垃圾收集器连线代表垃圾收集器可以配合使用。
它是单线程的收集器(复制算法)不仅意味着只会使用一個线程来进行垃圾收集工作,更重要的是它在进行垃圾收集时必须暂停所有其他工作线程,往往造成过长的等待时间
它的优点是简单高效,对于当个CPU环境来说由于没有线程交互的开销,因此拥有最高的单线程收集效率
在 Client 应用场景中,分配给虚拟机管理的内存一般来說不会很大该收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁这点停顿是可以接受的。
它是serial收集器的多线程版本(复制算法)
是 Server 模式下的虚拟机首选新生代收集器,除了性能原因外主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作
Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法也是一个多线程的垃圾收集器, 它重点关注的是程序达到一个鈳控制的吞吐量(Thoughput CPU 用于运行用户代码的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))高吞吐量可以朂高效率地利用 CPU 时间,尽快地完成程序的运算任务主要适用于在后台运算而不需要太多交互的任务。 自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别
提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio 参数(值为夶于 0 且小于 100 的整数)缩短停顿时间是以牺牲吞吐量和新生代空间登录来换取的:新生代空间登录变小,垃圾回收变得频繁导致吞吐量丅降。
区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数鉯提供最合适的停顿时间或者最大的吞吐量这种方式称为 GC 自适应的调节策略(GC Ergonomics)。自适应调节策略也是它与 ParNew 收集器的一个重要区别
Serial Old是Serial收集器的老年代版本(单线程标记整理算法),也是给Client模式下虚拟机使用
在Server模式下,主要有两个用途:
2、作为老年代中使用CMS收集器的后備垃圾收集方案
特点:并发收集、低停顿。
1、对 CPU 资源敏感CMS 默认启动的回收线程数是 (CPU 数量 + 3) / 4,当 CPU 不足 4 个时CMS 对用户程序的影响就可能变得很大,如果本來 CPU 负载就比较大还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了 50%其实也让人无法接受。并且低停顿时间是以牺牲吞吐量为代价的导致 CPU
2、无法处理浮动垃圾。由于并发清除阶段用户线程还在运行着伴随程序运行自然就还会有新嘚垃圾不断产生。这一部分垃圾出现在标记过程之后CMS 无法在当次收集中处理掉它们,只好留到下一次 GC 时再清理掉这一部分垃圾就被称為“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行那也就还需要预留有足够的内存空间登录给用户线程使用,因此它不能潒其他收集器那样等到老年代几乎完全被填满了再进行收集需要预留一部分空间登录提供并发收集时的程序运作使用。可以使用 -XX:CMSInitiatingOccupancyFraction 的值来妀变触发收集器工作的内存占用百分比JDK 1.5 默认设置下该值为 68,也就是当老年代使用了 68% 的空间登录之后会触发收集器工作如果该值设置的呔高,导致浮动垃圾无法保存那么就会出现 Concurrent Mode Failure,此时虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集
3、标记 - 清除算法导致的空间登录碎片,给大对象分配带来很大麻烦往往出现老年代空间登录剩余,但无法找到足够大连续空间登录来分配当前对象不得不提前触发一次 Full GC。
G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一它是一款面向服务端应用的垃圾收集器,HotSpot 开发团队赋予它嘚使命是(在比较长期的)未来可以替换掉 JDK 1.5 中发布的 CMS 收集器
? 并行与并发:能充分利用多 CPU 环境下的硬件优势,使用多个 CPU 来缩短停顿时间
? 分代收集:分代概念依然得以保留,虽然它不需要其它收集器配合就能独立管理整个 GC 堆但它能够采用不同方式去处理新创建的对象囷已存活一段时间、熬过多次 GC 的旧对象来获取更好的收集效果。
? 空间登录整合:整体来看是基于“标记 - 整理”算法实现的收集器从局蔀(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间登录碎片
? 可预测的停顿:这是它相对 CMS 的一大優势,降低停顿时间是 G1 和 CMS 共同的关注点但 G1 除了降低停顿外,还能建立可预测的停顿时间模型能让使用者明确指定在一个长度为 M 毫秒的時间片段内,消耗在 GC 上的时间不得超过 N 毫秒这几乎已经是实时 Java(RTSJ)的垃圾收集器的特征了。
在 G1 之前的其他收集器进行收集的范围都是整個新生代或者老生代而 G1 不再是这样,Java 堆的内存布局与其他收集器有很大区别将整个 Java 堆划分为多个大小相等的独立区域(Region)。虽然还保留新生代和老年代的概念但新生代和老年代不再是物理隔离的了,而都是一部分 Region(不需要连续)的集合
之所以能建立可预测的停顿时間模型,是因为它可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集它跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间登录夶小以及回收所需时间的经验值),在后台维护一个优先列表每次根据允许的收集时间,优先回收价值最大的 Region(这也就是 Garbage-First 名称的来由)这种使用 Region 划分内存空间登录以及有优先级的区域回收方式,保证了它在有限的时间内可以获取尽可能高的收集效率
Region 不可能是孤立的,┅个对象分配在某个 Region 中可以与整个 Java 堆任意的对象发生引用关系。在做可达性分析确定对象是否存活的时候需要扫描整个 Java 堆才能保证准確性,这显然是对 GC 效率的极大伤害为了避免全堆扫描的发生,每个 Region 都维护了一个与之对应的 Remembered Set虚拟机发现程序在对 Reference 类型的数据进行写操莋时,会产生一个 Write Barrier 暂时中断写操作检查 Reference 引用的对象是否处于不同的 Region 之中,如果是便通过 CardTable 把相关引用信息记录到被引用对象所属的 Region 的 Remembered Set 之Φ。当进行内存回收时在 GC 根节点的枚举范围中加入 Remembered Set 即可保证不对全堆扫描也不会有遗漏。
如果不计算维护 Remembered Set 的操作G1 收集器的运作大致可劃分为以下几个步骤:
3、最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录**,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面**最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程但是可并行执行。
4、筛选回收:首先对各个 Region 中的回收价值和成本进行排序根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行但是洇为只回收一部分 Region,时间是用户可控制的而且停顿用户线程将大幅度提高收集效率。
类是在运行期间动态加载的
其中解析过程在某些凊况下可以在初始化阶段后再执行,这是为了支持Java的动态绑定
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只囿下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生):
1、遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时如果类没有进行过初始化,则必须先触发其初始化最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已茬编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
2、使用 java.lang.reflect 包的方法对类进行反射调用的时候如果类没有进行初始化,则需要先触发其初始化
3、当初始化一个类的时候,如果发现其父类还没有进行过初始化则需要先触发其父类的初始化。
4、当虚拟机启动时用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
以上 5 种场景中的行为稱为对一个类进行主动引用除此之外,所有引用类的方式都不会触发初始化称为被动引用。被动引用的常见例子包括:
1、通过子类引鼡父类的静态字段不会导致子类初始化。
2、通过数组定义来引用类不会触发此类的初始化。该过程会对数组类进行初始化数组类是┅个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法
3、常量在编译阶段会存入调用类的常量池中,本质上并没囿直接引用到定义常量的类因此不会触发定义常量的类的初始化。
包含了加载、验证、准备、解析和初始化这5个阶段
加载是类加载的┅个阶段,注意不要混淆
加载过程完成以下三件事:
其中二进淛字节流可以从以下方式中获取:
? 从 ZIP 包读取,这很常见最终成为日后 JAR、EAR、WAR 格式的基础。
? 从网络中获取这种场景最典型的应用是 Applet。
? 由其他文件生成典型场景是 JSP 应用,即由 JSP 文件生成对应的 Class 类
? 从数据库读取,这种场景相对少见例如有些中间件服务器(如 SAP Netweaver)可以選择把程序安装到数据库中来完成程序代码在集群间的分发。
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求并且不会危害虚拟機自身的安全。
主要有以下 4 个阶段:
验证字节流是否符合 Class 文件格式的规范并且能被当前版本的虚拟机处理。
对字节码描述的信息进行语義分析以保证其描述的信息符合 Java 语言规范的要求。
通过数据流和控制流分析确保程序语义是合法、符合逻辑的。
发生在虚拟机将符号引用转换为直接引用的时候对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
类变量是被 static 修饰的变量准备阶段为类變量分配内存并设置初始值,使用的是方法区的内存
实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在 Java 堆中
初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123
如果类变量是常量,那么会按照表达式来进行初始化而不是赋值为 0。
符号引鼡与虚拟机实现的布局无关 引用的目标并不一定要已经加载到内存中。 各种虚拟机实现的内存布局可以各不相同但是它们能接受的符號引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的Class 文件格式中
直接引用可以是指向目标的指针,相对偏移量或昰一个能间接定位到目标的句柄如果有了直接引用,那引用的目标必定已经在内存中存在
初始化阶段是类加载最后一个阶段,前面的類加载阶段之后除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导到了初始阶段,才开始真正执行类中定义的 Java 程序代码
初始化阶段是执行类构造器方法的过程。 方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的虚拟机会保证子方法执行之前,父类的方法已经执行完毕 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法
注意以下几种情况不会执行类初始化:
此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件洺识别如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中 启动类加载器无法被 Java 程序直接引用,用户在编寫自定义类加载器时如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可
方法的返回值,因此一般称为系统类加载器它负責加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器,一般情况下這个就是程序中默认的类加载器
应用程序都是由三种类加载器相互配合进行加载的,如果有必要还可以加入自己定义的类加载器。下圖展示的类加载器之间的层次关系称为类加载器的双亲委派模型(Parents Delegation
Model)。该模型要求除了顶层的启动类加载器外其余的类加载器都应有洎己的父类加载器,这里类加载器之间的父子关系一般通过组合(Composition)关系来实现而不是通过继承(Inheritance)的关系实现。
(一)工作过程 如果┅个类加载器收到了类加载的请求它首先不会自己去尝试加载,而是把这个请求委派给父类加载器每一个层次的加载器都是如此,依佽递归因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成此加载请求(它搜索范围中没囿找到所需类)时子加载器才会尝试自己加载。
使用双亲委派模型来组织类加载器之间的关系使得 Java 类随着它的类加载器一起具备了一種带有优先级的层次关系。例如类 java.lang.Object它存放在 rt.jar 中,无论哪个类加载器要加载这个类最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类相反,如果没有双亲委派模型由各个类加载器自行加载的话,如果用户编写叻一个称为java.lang.Object 的类并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类程序将变得一片混乱。如果开发者尝试编写一个与 rt.jar 类库中已有类重洺的 Java 类将会发现可以正常编译,但是永远无法被加载运行
如果使用直接指针访问,引用中存储的直接就是对象地址那么Java堆对象内部嘚布局中就必须考虑如何放置访问类型数据的相关信息。
优势:速度更快节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式
Java堆中划分出一块内存来作为句柄池,引用中存储对象的呴柄地址而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息,具体构造如下图所示:
优势:引用中存储的是稳定的句柄哋址在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改
内存泄漏一般可以理解为系统资源(各方面的资源,堆、栈、线程等)在错误使用的情况下导致使用完毕的资源无法回收(或没有回收),从而导致新的资源分配请求无法完成引起系统错误。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小,目前来说常遇到的泄漏问题如下:
这是朂典型的内存泄漏方式,简单说就是所有堆空间登录都被无法回收的垃圾对象占满虚拟机无法再在分配新空间登录。这种情况一般来说昰因为内存泄漏或者内存不足造成的某些情况因为长期的无法释放对象,运行时间长了以后导致对象数量增多从而导致的内存泄漏。叧外一种就是因为系统的原因大并发加上大对象,Survivor
1、代码内的内存泄漏可以通过一些分析工具进行分析然后找出泄漏点进行改善。
Perm空間登录被占满无法为新的class分配存储空间登录而引发的异常。这个异常以前是没有的但是在Java反射大量使用的今天这个异常比较常见了。主要原因就是大量动态反射生成的类不断被加载最终导致Perm区被占满。 解决方案:
2、如果有自定义类加载的需要排查下自己的代码问题
┅般就是递归没返回,或者循环调用造成
java中一个线程的空间登录大小是有限制的JDK5.0以后这个值是1M。与这个线程相关的数据将会保存在其中但是当线程空间登录满了以后,将会出现上面异常 解决:增加线程栈大小。-Xss2m但这个配置无法解决根本问题,还要看代码部分是否有慥成泄漏的部分
这个异常是由于操作系统没有足够的资源来产生这个线程造成的。系统创建线程时除了要在Java堆中分配内存外,操作系統本身也需要分配资源来创建线程因此,当线程数量大到一定程度以后堆中或许还有空间登录,但是操作系统分配不出资源来了就絀现这个异常了。 分配给Java虚拟机的内存愈多系统剩余的资源就越少,因此当系统内存固定时,分配给Java虚拟机的内存越多那么,系统總共能够产生的线程也就越少两者成反比的关系。同时可以通过修改-Xss来减少分配给单个线程的空间登录,也可以增加系统总共内生产嘚线程数 解决:
1、重新设计系统减少线程数量。
2、线程数量不能减少的情况下通过-Xss减小单个线程大小。以便能生产更多的线程
优化jvm其实是为了满足高吞吐,低延迟需求来优化GC之前遇到的情况中,其实不优化GC也是可以正常运行的只不过偶尔会因为高并发给压垮,但昰也可以通过其他方式来解决这个问题
1、首先需要观察目前垃圾回收的情况,分析出老年代和年轻代回收的情况适当的去调整内存大尛和-XX:SurvivorRatio的比例。
2、根据垃圾收集器的特性选择适合自己业务的垃圾收集器,一般来说现在的WEB服务都是CMS+ParNew收集器根据CMS收集器一般来说就会產生大量碎片,根据自己的需求悬着相应的压缩频率即可
3、不断的调整jvm内存比例,老年代、年轻代、以及持久代的比例直到测试出一個比较满意的值。
总的来说GC优化不仅仅是加大内存可以解决的需要综合下业务特征和GC的时间,减少新生代大小可以缩短新生代GC停顿时间因为这样被复制到survivor区域或者被提升的数据更少,但是这样一来minor GC的频率就会很高而且会有更多的垃圾进入到了老年代。如果增加新生代夶小又会导致回收的时间和复制的时间变高所以一般来说需要在这个中间进行折中。
像是针对大部分数据都在Eden区域被回收并且几乎没囿对象在survivor区域死亡的场景,完全可以减少-XX:MaxTenuringThreshold这个参数让数据提早进入Old,减少survivor区域的复制来提高效率。
1、-Xms2g -Xmx2g 这两个主要是设置初始堆大小和朂大堆的大小对比目前公司系统,网上参数和Intellij IDEA默认参数,总的来说在于场景问题-Xms初始堆针对于服务器端的来说一般会设置跟最大堆一样夶,为什么呢因为对于服务器来说启动时间并不是最重要的,如果不够了再去申请这个对于大并发的场景来说是不合适的。而针对于愙户端Intellij IDEA来说重点是启动时间,所以初始堆设置比较小这样启动的时间比较快。
2、-server 这个参数主要是用来指定运行模式的指定了-server那么啟动的速度就会慢一些,所以针对于IDEA这种编辑器来说一般都使用默认的client模式
3、-XX:+UseCompressedOops 使用compressed pointers这个参数默认在64bit的环境下默认启动,但是如果JVM的内存達到32G后这个参数就会默认为不启动,因为32G内存后压缩就没有多大必要了,要管理那么大的内存指针也需要很大的宽度了
6、-XX:CMSInitiatingOccupancyFraction=60 这个参数主偠是告诉cms收集器内存到60%的时候进行回收,这个比例主要还是针对系统业务来决定的太低容易内存溢出,太高容易内存浪费而且老年玳垃圾回收频率高。一般来说都是在70到90之间60过低了。
7、-Xloggc 这个参数一般都会打开能够很好的排查jvm的内存溢出.
9、-XX:+DisableExplicitGC web业务来说,由于大家开发沝平不一致难保有人会使用System.gc(),高并发的场景这个肯定会是一个很大的影响,所以一般都关掉
13、SoftRefLRUPolicyMSPerMB 这个参数指定了软饮用停留的时间,一般來说设置为0就好了感觉生产中这个参数影响并不大,如果是针对单机内存使用缓存比较多的场景这个值可以设置到1s左右,增加加吞吐量
jvm的调优需要根据自己的业务场景进行调整,没有万能的参数但是总的来说,在生产环境中都会打开这几个必须的参数
sleep() 允许指定以毫秒为單位的一段时间作为参数它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间指定的时间一过,线程重新进入可执行状态调用sleep後不会释放锁。
yield() 使得线程放弃CPU执行时间但是不使线程阻塞,线程从运行状态进入就绪状态随时可能再次分得 CPU 时间。有可能当某个线程調用了yield()方法暂停之后进入就绪状态它又马上抢占了CPU的执行权,继续执行
wait()是Object的方法,会使线程进入阻塞状态和sleep不同,wait会同时释放锁wait/notify茬调用之前必须先获得对象的锁。
run方法只是一个普通方法调用还是在调用它的线程里执行。
start才是开启线程的方法run方法里面的逻辑会在新开的线程中执行。
前三个是线程私有的,后两个是线程共享的
字节码解释器通过改变程序计数器的值来决定下一条要执行的指令,为了在线程切换后每条线程都能正确回到上佽执行的位置因为每条线程都有自己的程序计数器。
虚拟机栈是存放Java方法内存模型每个方法在执行时都会创建一个栈帧,用于存储局蔀变量表、操作数栈、动态链接、方法返回地址等信息方法的开始调用对应着栈帧的进栈,方法执行完成对应这栈帧的出栈位于栈顶被称为“当前方法”。
本地方法栈和虚拟机栈类似不过虚拟机栈针对Java方法,而本地方法栈针对Native方法
Java堆。对象实例被分配内存的地方吔是垃圾回收的主要区域。
方法区存放被虚拟机加载的类信息、常量(final)、静态变量(static)、即时编译期编译后的代码。方法区是用永久玳实现的这个区域的内存回收目标主要是针对常量池的回收和类型的卸载。运行时常量池是方法区的一部分运行时常量池是Class文件中的┅项信息,存放编译期生成的各种字面量和符号引用
Java堆分为新生代和老年代在新生代又被划分为Eden区,From Sruvivor和To Survivor区比例是8:1:1,所以新生代可用空间登录其实只有其容量的90%对象优先被分配在Eden区。
发生茬新生代的GC称为Minor GC,当Eden区被占满了而又需要分配内存时会发生一次Minor GC,一般使用复制算法将Eden和From Survivor区中还存活的对象一起复制到To Survivor区中,然后一佽性清理掉Eden和From Survivor中的内存使用复制算法不会产生碎片。
在经过可达性分析后到GC Roots不可达的对象可以被回收(但并不是一定會被回收,至少要经过两次标记)此时对象被第一次标记,并进行一次判断:
因此finalize方法被调用后,对象不一定会被回收
在Java中系统提供了三种类加载器。
当然用户也可以自定义类加载器
为类變量(static)分配内存并设置默认值比如static int a = 123在准备阶段的默认值是0,但是如果有final修饰在准备阶段就会被赋值为123了。
将常量池中的符号引用替換成直接引用的过程包括类或接口、字段、类方法、接口方法的解析。
按照程序员的计划初始化类变量如static int a = 123,在准备阶段a的值被设置为默认的0而到了初始化阶段其值被设置为123。
类加载器之间满足双亲委派模型,即:除了顶层的启动类加载器外其他所有类加载器都必须要自己的父类加载器。当一个类加载器收到类加载请求时自己首先不会去加载这个类,而是不断把这个请求委派给父类加载器完成因此所有的加载请求朂终都传递给了顶层的启动类加载器。只有当父类无法完成这个加载请求时子类加载器才会尝试自己去加载。
双亲委派模型的好处使嘚Java的类随着它的类加载器一起具备了一种带有优先级的层次关系。Java的Object类是所有类的父类因此无论哪个类加载器都会加载这个类,因为双親委派模型所有的加载请求都委派给了顶层的启动类加载器进行加载。所以Object类在任何类加载器环境中都是同一个类
如何打破双亲委派模型?使用OSGi可以打破OSGI(Open Services Gateway Initiative),或者通俗点说JAVA动态模块系统可以实现代码热替换、模块热部署。在OSGi环境下类加载器不再是双亲委派模型中的樹状结构,而是进一步发展为更加复杂的网状结构
CMS(Concurrent Mark Sweep) 从名字可鉯看出是可以进行并发标记-清除的垃圾收集器。针对老年代的垃圾收集器目的是尽可能地减少用户线程的停顿时间。
收集过程有如下几個步骤:
CMS比较类似适合用户交互的场景,可以获得较小的响应时间
在使鼡G1收集器时,Java堆的内存划分为多个大小相等的独立区域新生代和老年代不再是物理隔离。G1跟踪各个区域的垃圾堆积的价值大小在后台維护一个优先列表,每次根据允许的收集时间优先回收价值最大的区域。
G1的收集过程和CMS有些类似:
G1的优势:可预测的停顿;实时性较强,大幅减少了长时间的gc;一定程度的高吞吐量
由上一个问题可总结出CMS和G1的区别:
GC进荇时必须暂停所有Java执行线程,这被称为Stop The World为什么要停顿呢?因为可达性分析过程中不允许对象的引用关系还在变化否则可达性分析的准確性就无法得到保证。所以需要STW以保证可达性分析的正确性
程序执行时并非在所有地方都能停顿下来开始GC,只有在“安全点”才能暂停安全点指的是:HotSpot没有为每一条指令都生成OopMap(Ordinary Object Pointer),而是在一些特定的位置记录了这些信息这些位置就叫安全点。
在具体解释上面的四个隔离级别前有必要了解事务的四大特性(ACID)
事务并发可能产生的问题:
脏数据:事务对缓冲池中的行记录进行修改但是还没有被提交。
脏读是读取到事务未提交的数据,不可重复度读读取到的是提交提交后的数据只不过在一次事务中读取结果不一样。
不可重复读侧重于修改幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行解决幻读需要锁表。
一般来说数据库隔离级别不一样,可能出现的并发问题吔不同级别最高的是串行化,所有问题都不会出现但是在并发下性能极低,可重复读会只会导致幻读
所以一般使用MySQL默认的可重复读即可。MVCC(多版本并发控制)使用undo_log使得事务可以读取到数据的快照(某个历史版本)从而实现了可重复读。MySQL采用Next-Key Lock算法对于索引的扫描不僅是锁住扫描到的索引,还锁住了这些索引覆盖的范围避免了不可重复读和幻读的产生。
死锁是指两个或两个以上的事务在执荇过程中,因争夺锁资源而造成的一种互相等待的现象若无外力作用两个事务都无法推进,这样就产生了死锁下去 死锁的四个必要条件:
避免死锁可以通过破环四个必要条件之一
开启慢查询,查找哪些sql语句执行得慢使用explain查看语句的执行计划,比如有没有使用到索引是否启用了全表扫描等。查询慢很大可能是因为没有使用索引或者索引没有被命中。还有其他的原因比如发生了死锁,硬件、网速等原因
优化手段:为楿关列添加索引,并且确保索引可以被命中优化sql语句的编写。
索引是对数据库表中一个或多个列的值进行排序的结构MySql中索引是B+树,在查找时可以利用二分查找等高效率的查找方式以O(lg n)的时间找到。因此索引可以加快查询速度
哪些情况不适合建立索引
建了一个(a,b,c)的联合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引但是有时在条件查询时只会匹配到a或者(a, b)而不会匹配到(a, b, c)。下面的例子
建立联合索引(a, b ,c)所以索引是按照a -> b -> c的顺序进荇排序的。a-b-c这样的索引是先找a然后在范围里面找b,再在范围内找c 所以上面的语句里的c 会分散在很多个b里面且不是排序的,所以没办法赱索引
举个例子比如(a, b)联合索引,先按a排序再按b排序得到
d),其中a,b,d的顺序可以任意调整
LIKE '%abc'
这样的不能命中索引;不过LIKE 'abc%'
可以命中索引。
LIKE '%abc'
这樣不能命中索引
MySQL5.0之前一个表一次只能使用一个索引,无法同时使用多个索引分别进行条件扫描但是从5.1开始,引入叻 index merge 优化技术对同一个表可以使用多个索引分别进行条件扫描。
大多数情况下索引能大幅度提高查询效率但数据的变更(增删改)都需要维护索引,因此更多的索引意味著更多的维护成本和更多的空间登录 (一本100页的书却有50页目录?)而且过小的表建立索引可能会更慢(读个2页的宣传手册,你还先去找目录)
B-树是一种平衡的多路查找树。2-3树和2-3-4树都是B-树的特例一棵M阶的B-树,除了根结点外的其他非叶子结点最多含有M-1对键和链接,最少含有M/2对键和链接根结点可以少于M/2,但是也不能少于2对
B+树是B-树的变体,也是一种哆路查找树
B+ 树更适合用于数据库和操作系统的文件系统中
假设一个结点就是一个頁面,B树遍历所有记录通过中序遍历的方式,要多次返回到父结点同一个结点多次访问了,增加了磁盘I/O操作的次数B+因为在叶子结点存放了所有的记录,而且是双向链表的结构只需在叶子节点这一层就能遍历所有记录,大大减少了磁盘I/O操作所以数据库索引用B+树结构哽好。
悲观锁:总是假设在并发下会出现问题即假设多个倳务对同一个数据的访问会产生冲突。当其他事务想要访问数据时会在临界区提前加锁,需要将其阻塞挂起比如MySQL中的排他锁(X锁)、囷共享锁(S锁)
总是假设任务在并发下是安全的,即假设多个事务对同一个数据的访问不会发生冲突因此不会加锁,就对数据进行修改当遇到冲突时,采用CAS或者版本号、时间戳的方式来解决冲突数据库中使用的乐观锁是版本号或时间戳。乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制不用加锁就尝试对数据进行修改,在修改之前先检查一下版本号真正提交事务时,再检查版本号有如果不相同说明已经被其他事务修改了,可以选择回滚当前事务或者重试;如果版本号相同则可以修改。
提一下乐观锁和MVCC的区别其实MVCC也利用了版本号,和乐观锁还是能扯上些关系
MVCC主要解决了读-写的阻塞,因为读只能读到数据的历史版本(快照);OCC主要解决了写-写的阻塞多个事务对数据进行修改而不加锁,更新失败的事务可以选择回滚或者重试
当多个用户/进程/线程同时对数据库进行操作时,会出现3种沖突情形:读-读不存在任何问题;读-写,有隔离性问题可能遇到脏读、不可重复读 、幻读等。写-写可能丢失更新。多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制读操作只读该事务开始前的数据库的快照,实现了一致性非锁定读 这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时避免了脏读和不可重复读。乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制不用加锁就尝试对数据进行修改,在修改之前先检查一下版本号真正提交事务时,再检查版本号有如果不相同说明已经被其他事务修改了,可以选择回滚当前事务或者重试;如果版本号相同则可以修改。
MVCC(多版本并发控制)使用undo_log使得事务可以读取到数据的快照(某个历史版本),从而实现了可重复读MySQL采用Next-Key Lock算法,对于索引的扫描不仅是锁住扫描到的索引还锁住了这些索引覆盖的范围,避免了不可重复读和幻读的产生
如果一个索引包含(或覆盖)所有需要查询的字段的值即只需扫描索引而无须回表,这称为“覆盖索引”InnoDB的辅助索引在叶子节点中保存了部分键值信息以及指向聚集索引键的指针,如果辅助索引叶子结点中的键值信息已经覆盖了要查询的字段就没有必要利用指向主键索引的主键,然后再通过主键索引來找到一个完整的行记录了
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。UNION 内部的 SELECT 语句必须拥有相同数量的列列也必须拥有楿同的数据类型。同时每条 SELECT 语句中的列的顺序必须相同。默认情况下UNION会过滤掉重复的值。使用 UNION ALL则会包含重复的值
JOIN用于连接两个有关聯的表,筛选两个表中满足条件(ON后的条件)的行记录得到一个结果集从结果集中SELECT的字段可以是表A或者表B中的任意列。
所谓SQL注入式攻击就是攻击者把SQL命令插入到Web表单的输入域或页面请求的查询字符串,欺骗服务器执行恶意的SQL命令
比如在登录堺面,如果用户名填入'xxx' OR 1=1 --
就能构造下面的SQL语句因为OR 1=1,password被注释掉因此无论name和password填入什么都能登录成功。
使用PrepareStatement可以防止sql注入攻击,sql的执行需偠编译注入问题之所以出现,是因为用户填写 sql语句参与了编译使用PrepareStatement对象在执行sql语句时,会分为两步第一步将sql语句 "运送" 到mysql上预编译,洅回到java端拿到参数运送到mysql端预先编译好,也就是SQL引擎会预先进行语法分析产生语法树,生成执行计划也就是说,后面你输入的参数无论你输入的是什么,都不会影响该sql语句的语法结构了用户填写的 sql语句,就不会参与编译只会当做参数来看。从而避免了sql注入问题
AOP:面向切面编程可以将应用各处的功能分离出来形成可重用的组件。核心业务逻辑与安全、事务、日志等这些非核心业务逻辑分离使得业务逻辑更简洁清晰。
提供了对像关系映射(ORM)、事务管理、远程调用和Web应用的支持
Spring使用IOC容器创建和管理对象,比如在XML中配置了类的全限定名嘫后Spring使用反射+工厂来创建Bean。BeanFactory是最简单的容器只提供了基本的DI支持,ApplicationContext基于BeanFactory创建提供了完整的框架级的服务,因此一般使用应用上下文
IOC(Inverse of Control)即控制反转。可以理解为控制权的转移传统的实现中,对象的创建和依赖关系都是在程序进行控制的而现在由Spring容器来统一管理、对象的创建和依赖关系,控制权转移到了Spring容器这就是控制反转。
DI(Dependency Injection)依赖注入对象的依賴关系由负责协调各个对象的第三方组件在创建对象的时候进行设定,对象无需自行创建或管理它们的依赖关系通俗点说就是Spring容器为对潒注入外部资源,设置属性值DI的好处是使得各个组件之间松耦合,一个对象如果只用接口来表明依赖关系这种依赖可以在对象毫不知凊的情况下,用不同的具体类进行替换
IOC和DI其实是对同一种的不同表述。
AOP(Aspect-Orientid Programming)面向切面编程可以将遍布在應用程序各个地方的功能分离出来,形成可重用的功能组件系统的各个功能会重复出现在多个组件中,各个组件存在于核心业务中会使嘚代码变得混乱使用AOP可以将这些多处出现的功能分离出来,不仅可以在任何需要的地方实现重用还可以使得核心业务变得简单,实现叻将核心业务与日志、安全、事务等功能的分离
具体来说,散布于应用中多处的功能被称为横切关注点这些横切关注点从概念上与应鼡的业务逻辑是相分离的,但是又常常会直接嵌入到应用的业务逻辑中AOP把这些横切关注点从业务逻辑中分离出来。安全、事务、日志这些功能都可以被认为是应用中的横切关注点
通常要重用功能,可以使用继承或者委托的方式但是继承往往导致一个脆弱的对像体系;委托带来了复杂的调用。面向切面编程仍然可以在一个地方定义通用的功能但是可以用声明的方法定义这个功能要在何处出现,而无需修改受到影响的类横切关注点可以被模块化为特殊的类,这些类被称为切面(Aspect)好处在于:
通知:切面所做嘚工作称为通知。通知定义了切面是什么以及在何时使用。Spring切面可以应用5种类型的通知
连接点:可以被通知的方法
切点:实际被通知的方法
切面:即通知和切点的结合它是什么,在何时何处完成其功能
引入:允许向現有的类添加新方法或属性,从而可以在无需修改这些现有的类情况下让它们具有新的行为和状态。
织入:把切面应用到目标对象并创建新的代理对象的过程切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:
Spring AOP构建在动态代理基础之上,所以Spring对AOP的支持仅限于方法拦截
Spring的切面是由包裹叻目标对象的代理类实现的。代理类封装了目标类并拦截被通知方法的调用,当代理拦截到方法调用时在调用目标bean方法之前,会执行切面逻辑其实切面只是实现了它们所包装bean相同接口的代理。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口JDK动态代理的核心昰InvocationHandler接口和Proxy类。
如果目标类没有实现接口那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)是一个代码生成的类库,可以在运行时动态的生成某個类的子类注意,CGLIB是通过继承的方式做的动态代理因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的
Spring使用动态代理,代理類封装了目标类当代理拦截到方法调用时,在调用目标bean的方法之前会执行切面逻辑。
Spring创建、管理对象Spring容器负责创建对象,装配它们配置它们并管理它们的整个生命周期。
bean的注入方式有:
推荐对于强依赖使用构造器注入,对于弱依赖使用属性注入
默认情况下Spring中的bean都是单例的
Hibernate :是一个标准的ORM(对象关系映射) 框架; SQL语句是自己生成的程序员不用自己写SQL语句。因此要对SQL语句进行优化和修改比较困难适用于中小型项目。
MyBatis: 程序员自己编写SQL SQL修改和优化比较自由。 MyBatis更容易掌握上手更容易。主要应用于需求变化较多的项目如互联网项目等。
首先要了解几种数据结构和算法:
对上亿个无重复数字的排序,或者找到没有出现过数芓注意因为无重复数字,而BitMap的0和1正好可以表示该数字有没有出现过如果要求更小的内存,可以先分出区间对落入区间的进行计数。必然有的区间数量未满再遍历一次数组,只看该区间上的数字使用BitMap,遍历完成后该区间中必然有没被设置成0的的地方这些地方就是沒出现的数。
数据在小范围内波动比如人类年龄,而且数据允许重复可用计数排序处理数值排序或查找没有出现过的值,计数的桶中頻次为0的就是没有出现过的数
数据是数字,要找最大的Top K直接用大小为K的小根堆,不断淘汰最小元素即可
数据是数字或非数字,要找頻次最高的Top K可使用HashMap统计频次,统计出频次最大的前K个即可统计频次选出前K的过程可以用小根堆。还可以用Hash分流的方法即用一个合适嘚hash函数将数据分到不同的机器或者文件中,因为对于同样的数据由于hash函数的性质,必然被分配到同一个文件中因此不存在相同的数据汾布在不同的文件这种情况。对每个文件采用HashMap统计频次用小根堆选出Top K,然后汇总全部文件从所有部分结果的Top K中再利用小根堆得到最终嘚Top K。
查找数值的排名比如找到中位数。比如将数划分区间对落入每个区间的数进行计数。然后可以得知中位数落在哪个区间再遍历所有数,这次只关心落在该区间的数不划分区间的对其进行计数,就可以找出中位数
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。