学习笔记(不定时更新))
Java虚拟机JVM学习笔记不少组织都曾开发过Java虚拟机SUN公司曾经使用过3个虚拟机Classic、Exact VM、Hotspot。其中Hotspot虚拟机沿用至今并已被Oracle合并到其新的虚拟机平台中与同样被Oracle收购的BEA公司的JRocket虚拟机合并。IBM的Java虚拟机叫J9。Apache也曾推出过一款叫做Apache Harmony的Java运行平台受到同样开源的OpenJDK的排挤已于2011年退出市场。Java虚拟机的基本结构1、类加载子系统2、Java内存程序计数器、Java堆、Java栈、本地方法栈、方法区、直接内存3、垃圾回收子系统4、执行引擎。Java堆一个Java程序的几乎所有对象都存放在堆中并且堆是完全自动化管理的不需要显式释放堆中的垃圾对象。在不同的虚拟机中或在不同的垃圾回收机制下Java堆可能具有不同的结构特征但最常见的组织结构是把整个堆分为新生代和老年代tenured。新生代中存放新创建的对象和“年龄”不大的对象老年代则存放“年龄”足够大的对象。新生代又分为eden、from、to三个区域其中from和to是两块大小相等、可以互换角色的内存空间。通常新创建的对象在eden区分配内存在一次新生代回收后如果对象还存活则会被转移到from区或to区。对象进入from区或to区之后没经过一次新生代回收如果对象还存活着则它的“年龄”加1。当“年龄”增长到一定程度后会被转移到老年代。Java栈Java栈是线程私有的内存空间。函数调用是线程执行的基本行为每次函数调用所需的数据都是通过Java栈传递的。Java栈和数据结构课程中所描述的栈有类似的特征都是一种先进后出的数据结构只支持入栈和出栈两种操作。栈帧是Java栈的基本单元。每次函数调用都会有一个栈帧入栈每次函数调用结束都会有一个栈帧出栈。无论函数是正常返回还是抛出异常都会有一个栈帧出栈。栈帧在一个栈帧中至少包含3个部分局部变量表、操作数栈、帧数据区。由于每次函数调用都会有栈帧入栈从而占据更多的栈空间显然如果栈空间不足以放置更多的栈帧时函数调用将无法继续下去这个时候会抛出StackOverflowError。Java虚拟机提供了“-Xss”参数用于在虚拟机启动的时候配置栈空间的最大值这个最大值决定了函数嵌套调用的深度。局部变量表局部变量表是栈帧的重要组成部分它用于保存函数的参数和局部变量。由于局部变量表在栈帧中如果函数的参数和局部变量过多会造成局部变量表膨胀从而栈帧变大函数嵌套调用的深度也会降低。操作数栈操作数栈也是栈帧的组成部分之一它被用来保存计算的中间结果作为计算过程中变量的临时存储空间。操作数栈也是先进后出的很多Java字节码指令都需要通过操作数栈进行参数传递。帧数据区帧数据区中保存着常量池的指针方便字节码指令访问常量池。帧数据区中保存了一张异常处理表方便在发生异常时找到处理异常的代码。栈上分配栈上分配是Java虚拟机提供的优化技术。它的思路是可以把某些线程私有对象分配在栈上借助退栈操作直接清理该对象而不需要垃圾回收子系统的介入减少GC从而提高系统性能。栈上分配的技术基础是逃逸分析逃逸分析用于判断某个对象是否能够被其他线程访问到。如果某个对象能够被其他线程访问到则它是一个逃逸对象不能分配在栈上。顺便提一下书上说只有Java虚拟机运行在Server模式下的时候才可以启用逃逸分析。方法区方法区和Java堆一样是线程共享的内存区域。它主要用于保存Java类信息字段、方法等。方法区的大小决定了虚拟机中可以加载多少Java类。如果有太多的Java类需要加载通常使用了某些动态代理技术时会动态产生过多的类信息会抛出内存溢出错误。虚拟机参数-XX:PermSize和-XX:MaxPermSize用于指定方法区的最小和最大尺寸。默认情况下最大64MB。在JDK1.6和1.7中方法区也叫永久区。在JDK1.8、1.9和1.10中永久区已被“元数据区”取代元数据区是直接内存。和永久区不同的是元数据区可以扩展至耗尽所有系统内存。跟踪调试参数Java虚拟机提供了大量用于跟踪系统状态的参数配置使用这些参数可以在Java程序运行过程中打印相关日志用于问题分析。GC参数GC垃圾回收机制是Java的一大特色可以帮助程序员管理内存。但是当GC对程序本身造成影响GC过于频繁、CPU过载等时需要进行分析处理。打印GC日志-XX:PrintGC使用这个参数启动虚拟机只要遇到GC就会打印日志。-Xlog:gc这个参数是用来给JDK9和JDK10打印GC日志的因为这两个版本的JDK默认使用G1收集器。打印详细GC日志的参数是-XX:PrintGCDetailsJDK9和JDK10中对应的参数是-Xlog:gc*。跟踪类的加载、卸载-verbose:class参数可以跟踪类的加载和卸载。单独使用-XX:TraceClassLoading参数JDK9和JDK10中对应的参数是-Xlog:classloadinfo跟踪类的加载情况。单独使用-XX:TraceClassUnloading参数JDK9和JDK10中对应的参数是-Xlog:classunloadinfo跟踪类的卸载情况。查看系统参数-XX:PrintVMOptions参数在程序运行时打印虚拟机接收到的命令行显式参数列表。-XX:PrintCommandLineFlags参数打印传递给虚拟机的显式和隐式参数。-XX:PrintFlagsFinal参数打印所有的系统参数值。Java堆大小设置当Java进程启动时虚拟机会给该进程分配一块初始堆空间当初始堆空间耗尽后虚拟机会扩展堆空间扩展后的堆空间大小不能超过最大堆空间。-Xms参数在Java进程启动时指定初始堆空间大小。-Xmx参数指定最大堆空间大小。新生代的配置-Xmn参数用于设置新生代的大小。-XX:SurvivorRatio参数用于指定新生代中eden区和from/to区的比例即-XX:SurvivorRatioeden/fromeden/to新生代的大小一般设置为整个堆空间的四分之一到三分之一新生代的大小对GC有很大影响基本上应该尽可能将对象预留在新生代以减少老年代GC的次数。设置较大的新生代会减小老年的大小。-Xmn参数可以指定新生代的绝对大小还可以使用-XX:NewRatio参数来设置新生代和老年代的比例-XX:NewRatio老年代/新生代堆溢出Java程序在运行过程中如果遇到堆内存不足的情况时则会抛出内存溢出错误OOMOut Of Memory。对于OOMJava虚拟机提供了-XX:HeapDumpOnOutOfMemoryError参数用于在发生OOM时导出整个堆信息。这个参数配合另一个参数-XX:HeapDumpPath指定导出堆的存放路径java -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPathd:/java.dump ...另外还有一个参数-XX:OnOutOfMemoryError在发生错误时调用一个脚本文件用于善后处理。java -XX:OnOutOfMemoryErrord:/abc.bat ...方法区配置在JDK1.6和JDK1.7中可以使用下面两个参数设置方法区大小的下限和上限-XX:PermSize-XX:MaxPermSizeJDK1.8以后方法区已被废弃类信息存放在元数据区中。元数据区只受系统可用内存的限制。可以使用-XX:MaxMetaspaceSize参数设置元数据区的最大可用值。直接内存配置直接内存跳过Java堆直接操作系统原生内存。从一定程度上加快了内存的访问速度。但是认为使用直接内存一定可以提高性能是错误的。一般来说直接内存的访问速度比Java堆内存要快但是在内存申请时直接内存毫无优势可言。所以看直接内存适合申请次数较少、访问较频繁的场景。-XX:MaxDirectMemorySize参数可以限定直接内存的最大值如果不设置这个参数则默认最大值为-Xmx参数的值。当已使用的直接内存到达指定的最大值时会触发GCGC后如果仍旧内存不足会引起OOM。Java虚拟机的工作模式Java虚拟机支持Client和Server两种运行模式。这两种运行模式可以通过两个参数分别指定-client参数-server参数因为以Server模式启动Java程序时会尝试手机更多的系统性能信息使用更复杂的优化算法所以Server模式启动程序的速度要慢于Client模式。但是也正是因为这样以Server模式启动的Java程序要远远快于以Client模式启动的Java程序。常见垃圾回收算法1、引用计数法Reference Counting2、标记清除法Mark-Sweep3、复制算法Copying4、标记压缩法Mark-Compact5、分代算法Generationall Collecting6、分区算法Region。引用计数法引用计数法是最经典且古老的一种垃圾收集算法。引用计数法的基本思想是给对象设置一个引用次数的变量。对象被赋值给其他变量引用次数加一引用失效后引用次数减一。但引用次数变成零的时候代表对象不再被任何变量引用可以直接回收该对象占用的内存空间。引用计数法的最严重问题是无法处理循环引用的问题。标记清除法标记清除法是现代垃圾收集算法的思想基础。标记清除法分两个阶段标记阶段和清除阶段。在标记阶段算法通过根节点标记所有从根节点开始的可达对象。在清除阶段回收所有未被标记的对象所占用的内存空间。标记清除法的最大问题是可能产生大量的内存碎片。复制算法复制算法的核心思想是将这个内存空间分成两块每次只使用其中的一块。当需要垃圾回收时将正在使用的那一块中所有的存活对象都复制到另一块内存空间中然后清理当前内存块中的所有对象。之后交换两块内存的角色GC完成。复制算法非常符合新生代垃圾回收的要求因为新生代里的垃圾对象通常远远多于存活对象因此更有效率。之前介绍过新生代被分成了三块eden、from、to。其中的from区和to区可以视为复制算法中的两块大小相同、地位相等且可以互换角色的内存空间。标记压缩法标记压缩法是一种老年代的回收算法。它是标记清除法的优化版。标记压缩法在执行了标记清除法的两个阶段后还会把存活对象压缩到内存的一端从而避免了碎片的产生。也就是说标记压缩法标记清除法碎片整理。分代算法分代算法本身并不是一种新的垃圾回收算法。它的思路是根据对象的特点把内存分成不同的块然后根据每块内存中对象的特点选择不同的垃圾回收算法以提高效率。分代算法被现代虚拟机广泛使用几乎所有的垃圾回收器都区分新生代和老年代。通常新生代会频繁发生垃圾回收但是每次垃圾回收耗时都比较短而老年代的垃圾回收并不频繁但是只要发生垃圾回收都会消耗很长时间。分区算法通常内存空间越大一次垃圾回收的耗时就越长。分区算法的思路是把这个内存空间分割成若干小的区间每次需要垃圾回收时都选择其中的部分小区间进行回收而不是回收整个内存空间。这样就有效控制了每次垃圾回收所产生的停顿时间。可触及性一个相对是否可以被当做垃圾回收需要考察这个对象的可触及性即在任何情况下从根节点开始是否可以访问到这个对象。如果在任何情况下从所有根节点开始都无法直接或间接访问到这个对象则说明这个对象已经不再被使用了该对象可以被回收。可触及性设计三种状态1、可触及从根节点开始可以到达该对象则称该对象可触及2、可复活对象的所有引用都已释放但是该对象可以通过调用其finalize方法被复活3、不可触及finalize方法已被调用finalize只可能被虚拟机调用一次但没有被复活该对象就进入不可触及状态。finalize方法finalize方法是一个非常糟糕的反模式不推荐使用它来释放资源。首先finalize方法很可能在无意中复活对象。其次finalize方法是由系统调用的调用时机不明确。引用强度Java中提供了4个级别的引用1、强引用2、软引用3、弱引用4、虚引用。强引用强引用就是一般的引用它不会被垃圾回收器回收。软引用java.lang.ref.SoftReference软引用比强引用稍微弱一点。如果某对象只持有软引用那么当堆空间不足时就会被回收因此软引用对象不会引起内存溢出。弱引用java.lang.ref.WeakReference弱引用比软引用还要弱。在垃圾回收时不管系统内存的使用情况如何只要发现弱引用就会立即对其进行回收。弱引用之所以可以在内存中存在是因为垃圾回收线程的优先级很低不能立即发现它。虚引用java.lang.ref.PhantomReference虚引用是所有引用类型中最弱的一个。虚引用和没有引用几乎是一样的随时都可能被回收。试图通过虚引用的get方法取得强引用时总会失败。虚引用只是为了跟踪垃圾回收过程。垃圾回收器1、串行回收器2、并行回收器3、CMS回收器4、G1回收器。串行回收器每次垃圾回收时串行回收器都只有一个工作线程。对于并行能力差的机器使用串行回收器反而性能更高、效率更好。因为机器并行能力差启动多个线程并行回收反而多了线程切换的开销。串行回收器是独占式的垃圾回收过程中所有其他线程都要停止工作。串行回收器既可以用于新生代也可以用于老年代。新生代串行回收器新生代串行回收器使用复制算法容易实现、逻辑处理高效且没有线程切换的开销。虚拟机以Client模式启动时串行回收器是默认回收器。老年代串行回收器老年代串行回收器使用标记压缩算法。可以使用以下三种参数指明老年代使用串行回收器1、-XX:UseSerialGC参数新生代、老年代都使用串行回收器2、-XX:UseParNewGC参数JDK9和JDK10中该参数已作废新生代使用ParNew回收器老年代使用串行回收器3、-XX:UseParallelGC参数指定新生代使用ParallelGC回收器老年代使用串行回收器。并行回收器并行回收器使用多个线程同时进行垃圾回收对于并行能力更强的机型来说可以有效减少每次垃圾回收所需的时间。新生代并行回收器ParNewParNew回收器是新生代垃圾回收器使用复制算法它只是简单地将新生代串行回收器多线程化。ParNew回收器也是独占式的当它启动时其他所有线程都要暂停。-XX:UseConcMarkSweepGCJDK9、JDK10中已不建议再使用该参数新生代使用ParNew回收器老年代使用CMS回收器。ParNew回收器的线程数可以通过-XX:ParallelGCThreads参数指定。该回收器的线程数一般需要与计算机的CPU核数关联一般情况下1、当CPU核数小于8时ParallelGCThreads的值取CPU的核数2、当CPU核数大于8时ParallelGCThreads的值等于3(5*CPU_count/8)。新生代并行回收器ParallelGCParallelGC回收器也使用复制算法。看上去ParallelGC回收器和ParNew回收器很相似。不同的是ParallelGC回收器非常关注系统的吞吐量。新生代ParallelGC回收器可以使用两种参数启用1、-XX:UseParallelGC新生代使用ParallelGC回收器老年代使用串行回收器。2、-XX:UseParallelOldGC新生代使用ParallelGC回收器老年代使用ParallelOldGC回收器。ParallelGC回收器提供了两个参数用于控制系统吞吐量1、-XX:MaxGCPauseMillis设置最大停顿时间该参数值是一个大于0的整数。指定该参数后回收器会自动调整Java堆的大小或其他系统参数尽量把停顿时间控制在MaxGCPauseMillis以内。MaxGCPauseMillis不能设置得过小因为过小的话回收器会把堆调整得很小这样会造成垃圾回收频繁系统吞吐量反而更低了。2、-XX:GCTimeRatio设置吞吐量大小该参数值是一个0到100以内的整数。系统将花费不超过1/(1GCTimeRatio)的时间进行垃圾回收。默认GCTimeRatio到取值为99即有不超过1/(199)1%的时间用于垃圾回收。-XX:MaxGCPauseMillis参数和-XX:GCTimeRatio参数是互相矛盾的因为如果减小每次垃圾回收的时间吞吐量也会降低而增加吞吐量又会延长每次垃圾回收的时间。ParallelGC回收器还可以通过-XX:UseAdaptiveSizePolicy参数打开自适应GC策略让虚拟机自行完成GC调优工作。老年代并行回收器ParallelOldGCParallelOldGC回收器使用标记压缩算法。ParallelOldGC回收器也是多线程的和新生代ParallelGC回收器一样它也关注吞吐量。CMS回收器Concurrent Mark Sweep、并发标记清除CMS回收器使用标记清除算法。CMS回收器主要关注系统停顿时间。CMS回收器的工作过程CMS回收器工作时的主要步骤有1、初始标记独占式2、并发标记3、预清理4、重新标记独占式5、并发清除6、并发重置。启用CMS回收器使用参数-XX:UseConcMarkSweepGC可以声明启用CMS回收器。并行和并发的区别并发是指回收器和应用线程交替执行并行是指应用程序完全挂起同时由多个线程一起执行GC。CMS回收器的执行CMS回收器不是独占式的在CMS回收过程中应用线程任然可以继续执行。应用线程执行过程中又会产生垃圾对象这些新的垃圾对象在当前的CMS回收过程中是无法清理的。所以在CMS回收过程中要确保应用线程有足够的内存可用。因此CMS回收器不会等到内存耗尽的时候才进行垃圾回收而是在内存使用率到达一定程度时就开始垃圾回收了。可以通过参数-XX:CMSInitiatingOccupancyFraction指定一个阈值默认68即内存使用率到达68%时CMS回收器就会执行。CMS阈值的设置原则如果内存使用率飞速增长在CMS回收过程中已经出现了内存不足的状况CMS回收就会失败。CMS失败后虚拟机会启动老年代串行回收器进行垃圾回收这时应用线程会被全部挂起造成很长的停顿时间。如果内存使用率上升缓慢可以将CMSInitiatingOccupancyFraction设大点以有效降低CMS的触发频率减少老年代回收次数。如果内存使用率上升飞快可以将CMSInitiatingOccupancyFraction设小点以避免老年代串行回收器的触发频率。CMS内存压缩参数CMS回收器使用的是标记清除算法这种算法容易产生内存碎片。为了减少碎片CMS回收器提供了两个用于压缩内存的参数1、-XX:UseCMSCompactAtFullCollectionCMS垃圾收集完成后进行一次碎片整理非并发。2、-XX:CMSFullGCBeforeCompaction设定进行了多少次CMS回收后进行一次内存压缩。G1回收器Garbage FirstG1回收器是JDK1.7中引入的可以替代CMS回收器。G1仍旧属于分代回收器。从堆的结构上看G1回收器采用了分区算法新生代和老年代都不必连续。G1兼顾新生代和老年代其它回收器要么只工作在新生代要么只工作在老年代。G1每次回收都会进行有效的内存复制减少内存碎片。由于分区G1可以每次只回收部分区块缩小了回收范围可以有效控制GC停顿。G1相关参数-XX:UseG1GC该参数用于声明使用G1回收器。-XX:MaxGCPauseMillis该参数用于指定最大停顿时间。在实际运行过程中如果任何一次停顿超过MaxGCPauseMillisG1就会调整新生代和老年代的比例、调整堆大小、调整晋升年龄等以符合预设目标。-XX:InitiatingHeapOccupancyPercent这个参数用于指定当整个堆使用率达到多少时触发并发标记周期。InitiatingHeapOccupancyPercent默认值是45即当整个堆的使用率达到45%时触发并发标记周期。InitiatingHeapOccupancyPercent一旦被设置G1就不会再修改它了。禁用System.gc()默认情况下System.gc()会显式触发Full GC同时对老年代和新生代进行回收。使用-XX:-DisableExplicitGC可以禁用显式GC它使System.gc()等价于一个空函数调用。调用System.gc()时使用并发回收默认情况下如果没有禁用System.gc()会使用传统的Full GC方式回收整个堆。使用参数-XX:ExplicitGCInvokesConcurrent可以使用并发方式进行回收。不使用这个参数即使启用了CMS或G1也不会进行并发回收。TLAB线程本地分配缓存Thread Local Allocation BufferTLAB是线程专属的内存分配区域。在同一时刻通常会有大量的线程在对上申请内存空间所以每一次内存分配都必须进行加锁同步加锁是效率低下的。TLAB由于是线程专属的其他线程无法访问所以能够不用加锁就进行内存分配。TLAB在eden区。青蛙客服系统https://download.csdn.net/download/look4liming/93326820