给人做网站网站,最打动人心的广告语,局网站内容建设考核,软件资源1.jvm内存结构#xff1a;程序计数器、虚拟机栈、本地方法栈、java堆、方法区
程序计数器#xff1a;线程私有的#xff0c;一块很小的内存空间#xff0c;作为当前线程的行号指示器#xff0c;用于记录当前虚拟机正在执行的线程指令地址。虚拟机栈#xff1a;线程私有的…1.jvm内存结构程序计数器、虚拟机栈、本地方法栈、java堆、方法区
程序计数器线程私有的一块很小的内存空间作为当前线程的行号指示器用于记录当前虚拟机正在执行的线程指令地址。虚拟机栈线程私有的每个方法执行的时候都会创建一个栈帧用于存储局部变量表操作数动态链接和方法返回等信息当线程请求的栈深度超过了虚拟机允许的最大深度时就会抛出StackOverFlowError本地方法栈线程私有的保存的是native方法的信息当一个jvm创建的线程调用native方法后jvm不会在虚拟机栈中为该线程创建栈帧而是简单的动态链接并直接调用该方法。java堆是所有线程共享的一块内存几乎所有对象的实例和数组都要在堆上分配内存因此该区域经常发生垃圾回收的操作方法区存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据即永久代
2.jvm内存模型JMM
在底层处理器内存模型的基础上定义自己的多线程语义明确指定了一组排序规则来保证线程间的可见性。happens-before要想保证b操作能够看到a操作的结果a和b之间必须满足happens-before 单线程规则一个线程中的每个动作都happens-before该线程中后续的每个动作监视器锁定规则监听器的解锁动作happens-before后续对这个监听器的锁定动作volatile变量规则对volatile字段的写入动作happens-before后续对这个字段的每个读取动作线程start规则线程start方法的执行happens-before一个启动线程内的任意动作线程join规则一个线程内的所有动作happens-before任意其他线程在该线程join成功返回之前传递性如果A happens-before B 且B happens-before C 那么A happens-before C 关键字volatilefinal和synchronized volatile保证可见性和有序性synchronized保证可见性和有序性通过管程保证一组动作的原子性final通过禁止在构造函数初始化和给final字段赋值这两个动作的重排序保证可见性如果this引用逃逸就不好说可见性了编译器遇到这些关键字时会插入相应的内存屏障保证语义的正确性注synchronized不保证同步块内的代码禁止重排序因为它通过锁保证同一时刻只有一个线程访问同步块临界区即同步块的代码只满足as-if-serial语义-只要单线程的执行结果不改变可以进行重排序
java内存模型描述的是多线程对共享内存修改后彼此之间的可见性这确保正确同步的java代码可以在不同体系结构的处理器上正确运行。
3.heap和stack
申请方式 stack由系统自动分配例如声明在函数中一个局部变量int b系统自动在栈中为b开辟空间heap需要程序员自己申请并指明大小在c中malloc函数对于java要手动new Object的形式开辟 申请后系统的响应 stack只要栈的剩余空间大于所申请空间系统将为程序提供内存否则将报异常提示栈溢出heap操作系统有一个记录空闲内存地址的链表当系统受到程序的申请时会遍历该链表寻找第一个空间大于所申请空间的堆节点然后将该节点从空闲节点链表中删除并将该结点的空间分配给程序另外由于找到的堆节点的大小不一定正好等于申请的大小系统会自动的将多余的那部分重新放入空闲链表中。 申请大小的限制 stack栈是向低地址扩展的数据结构是一块连续的内存的区域heap堆是向高地址扩展的数据结构是不连续的内存区域 申请效率的比较 stack由系统自动分配速度较快但程序员无法控制heap由new分配的内存一般速度比较慢而且容易产生内存碎片不过用起来最方便 heap和stack中存储内容 stack函数调用时第一个进栈的是主函数中后的下一条指令的地址然后还是函数的各个参数heap一般是在堆的头部用一个字节存放堆的大小
4.栈内存溢出
栈是线程私有的栈的生命周期和线程一样每个方法在执行的时候创建一个栈帧包含局部变量表操作数栈动态链接方法出口等信息局部变量表又包括基本数据类型和对象的引用当线程请求的栈深度超过了虚拟机允许的最大深度时会抛出StackOverFlowError异常方法递归调用可能会出现该问题调整参数-xss去调整jvm栈的大小
5.OOM
除了程序计数器其他内存区域都有oom的风险
栈一般经常发生StackOverflowErroreg:32位windows系统单进程限制2G内存无限创建线程就会发生栈的OOMjava8常量池移到堆中溢出会出java.lang.OutOfMemoryErrorJava heap space设置最大元空间参数无效堆内存溢出GC之后无法在堆中申请内存创建对象就会报错方法区oom动态生成大量的类jsp等直接内存oom涉及到-xxMaxDirectMemorySize参数和Unsafe对象对内存的申请
排查oom的方法
增加两个参数 -xxHeapDumpOnOutOfMemoryError-xxHeapDumpPath/tmp/heapdump.hprof当oom发生时自动dump堆内存信息到指定目录 同时jstat查看监控jvm的内存和gc情况先观察问题大概出现在什么区域使用MAT工具载入到dump文件分析大对象的占用情况比如hashmap做缓存未清理时间长了就会内存溢出可以把他改为弱引用。
6.JVM的常量池
class文件常量池class文件是一组以字节为单位的二进制数据流在java代码编译期间编写的java文件就被编译为.class文件格式的二进制数据存放在磁盘中其中就包括class文件常量池。运行时常量池运行时常量池相对于class常量池一大特征就是具有动态性java规范并不要求常量只能在运行时才产生运行时常量池的内容并不全部来自class常量池在运行时可以通过代码生成常量并将其放入运行时常量池中这种特性被用的最多的就是String.intern()。全局字符串常量池JVM所维护的一个字符串实例的引用表在HotSpot VM中是一个叫做StringTable的全局表。在字符串常量池中维护的是字符串实例的引用底层C实现就是一个Hashtable。这些被维护的引用所指的字符串实例被称作”被驻留的字符串”或”interned string”或通常所说的”进入了字符串常量池的字符串”。基本类型包装类对象常量池java中基本类型的包装类的大部分都实现了常量池技术这些类是 Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。这5种整型的包装类也只是在对应值小于等于127时才可使用对象池也即对象不负责创建和管理大于127的这些类的对象。
7.判断一个对象是否存活 引用计数法引用计数器为0说明这个对象没有引用无法解决循环引用问题 可达性分析法一个对象在GCroots没有任何引用链相连接时说明对象不可用 虚拟机栈中引用的对象方法区类静态属性引用的变量方法区常量池引用的对象本地方法栈JNI引用的对象 一个对象满足上述条件时不会马上被回收还需两次标记 ①判断当前对象是否有finalize方法并且该方法没有被执行过若不存在则标记为垃圾对象等待回收若有的话第二次标记。 ②将当前对象放入F-Queue队列并生成一个finalize线程去执行该方法虚拟机不保证该方法一定会被执行因为如果线程执行缓慢或进入了死锁会导致回收系统的崩溃如果执行了finalize方法后仍然没有与GC roots有直接或间接的引用则该对象会被回收。
8.引用
强引用普通的对象引用关系String s new String“xxx”;软引用用于维护一些可有可无的对象只有内存不足时系统则会回收软引用对象如果回收了软引用对象之后仍然没有足够的内存才会抛出内存溢出异常弱引用拥有更短的生命周期当jvm垃圾回收时无论内存是否充足都会回收被弱引用关联的对象虚引用主要用来跟踪对象被垃圾回收的活动
9.垃圾回收算法标记清除法标记整理法复制算法分代收集算法
标记清除法 利用可达性去遍历内存把存活对象和垃圾对象进行标记再遍历一遍将所有标记对象回收掉效率不行标记和清除的效率都不高标记和清除后会产生大量的不连续空间分片可能会导致之后程序运行的时候需分配大对象而找不到连续分片而不得不触发一次GC 标记整理法 利用可达性去遍历内存把存活对象和垃圾对象进行标记将所有的存活的对象向一端移动将端边界以外的对象都回收掉使用于存活对象多垃圾少的情况需要整理的过程无空间碎片产生 复制算法 将内存按照容量大小分成大小相等的两块每次只使用一块当一块使用完了就将还存活的对象移到另一块上然后把使用过的内存空间移除不会产生空间碎片内存使用率极低 分代收集算法 根据内存对象的存活周期不同java虚拟机一般将内存分成新生代和老年代 在新生代中有大量对象死去和少量对象存活所以采用复制算法只需要付出少量存活对象的复制成本就可与完成收集老年代中因为对象的存活率极高没有额外的空间对他进行分配担保所以采用标记清理或标记整理算法回收。
10.垃圾回收器
Serial单线程收集器收集垃圾时必须stop the world使用复制算法最大特点是进行垃圾回收时需要对所有正在执行的线程暂停对于有些应用是难以接受的。多数可以接受是client级别的默认gc方式。ParNewSerial收集器的多线程版本也需要stop the worldParallel Scavenge新生代收集器复制算法的收集器并发的多线程收集器目标是达到一个可控的吞吐量和ParNew最大的区别是GC自动调节策略虚拟机会根据系统的运行状态收集性能监控信息动态设置做这些参数以提供最优停顿时间和最高吞吐量。Serial Oldserial收集器的老年代版本单线程收集器使用标记整理算法。Parallel Old是parallel scavenge收集器的老年代版本使用多线程标记-整理算法CMS是一种以获得最短回收停顿时间为目标收集器标记清除算法过程: 初始标记并发标记重新标记并发清除收集结束会产生大量空间碎片 G1标记整理算法流程: 初始标记并发标记最终标记筛选回收不会产生空间碎片可以精确地控制停顿。G1将整个堆分为大小相等的多个regionG1跟踪每个区域的垃圾大小在后台维护一个优先级列表每次根据允许的收集时间优先回收价值最大的区域已达到在有限时间内获取尽可能高的回收效率
11.垃圾回收器-CMS
CMS并发标记清除以获取最短回收停顿时间为目标的收集器在垃圾收集时使得用户线程和GC线程并发执行因此在垃圾收集过程中用户也不会感到明显的卡顿。回收过程 初始标记标记GCRoot开始的下级对象过程中会STW但是跟GC Root直接关联的下级对象不会很多过程很快。并发标记根据上一步结果继续向下标识所有关联的对象直到这条链上的最尽头多线程不阻塞没有STW重新标记再标记一次第二部并没有阻塞其他工作线程其他的线程在标识过程中很有可能会产生新的垃圾并发清除清理删除掉标记阶段的已经死亡的对象由于不需要移动存活对象所以这个阶段也是可以与用户线程同时并发进行的。 问题 并发回收导致cpu资源紧张无法清理浮动垃圾并发失败内存碎片问题
12.垃圾回收器-G1
过程 初始标记仅仅标记一下GC Roots能直接关联到的对象并修改TAMS指针的值让下一阶段用户线程并发运行时能正确地在可用的Region中分配新对象这个阶段需停顿线程但耗时很短借用进行Minor GC时候同步完成所以G1收集器在这个阶段实际并没有额外的停顿并发标记从GC Roots开始对堆中对象进行可达性分析递归扫描整个堆里的对象图找出要回收的对象阶段耗时较长但可与用户程序并发执行当对象图扫描完成以后还要重新在并发时有引用变动的对象最终标记对用户线程做短暂的暂停处理并发阶段结束后仍有引用变动的对象清理标记更新region统计数据对各个region的回收价值和成本进行排序根据用户所期望的停顿时间来制定回收计划可以自由选择任意多个region构成回收集然后把决定回收的那一部分region的存活对象复制到空的region中再清理掉整个旧region的全部空间这里的操作涉及存活对象的移动必须暂停用户线程由多条回收器线程并行完成的
13.JVM一次完整的GC
Java堆内存划分
新生代 ( Young )占总空间的1/3 3 个分区Eden、To Survivor、From Survivor默认占比是 8:1:1。 新生代的垃圾回收Minor GC后只有少量对象存活选用复制算法只需要少量的复制成本就可以完成回收。 老年代 ( Old )老年代占 2/3。 老年代的垃圾回收Major GC使用“标记-清理”或“标记-整理”算法。
转化流程
对象优先在Eden分配。当 eden 区没有足够空间进行分配时虚拟机将发起一次 Minor GC。 在 Eden 区执行了第一次 GC 之后存活的对象会被移动到其中一个 Survivor 分区Eden 区再次 GC这时会采用复制算法将 Eden 和 from 区一起清理存活的对象会被复制到 to 区移动一次对象年龄加 1对象年龄大于一定阀值会直接移动到老年代。GC年龄的阀值可以通过参数 -XX:MaxTenuringThreshold 设置默认为 15动态对象年龄判定Survivor 区相同年龄所有对象大小的总和 (Survivor 区内存大小 * 这个目标使用率)时大于或等于该年龄的对象直接进入老年代。其中这个使用率通过 -XX:TargetSurvivorRatio 指定默认为 50%Survivor 区内存不足会发生担保分配超过指定大小的对象可以直接进入老年代。 大对象直接进入老年代大对象就是需要大量连续内存空间的对象比如字符串、数组为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。老年代满了而无法容纳更多的对象Minor GC 之后通常就会进行Full GCFull GC 清理整个内存堆 – 包括年轻代和老年代。
14.Minor GC 和Full GC有什么不同
Minor GC 只收集新生代的GC 触发条件当Eden区满时触发Minor GC。 Full GC收集整个堆包括 新生代老年代永久代(在 JDK 1.8及以后永久代被移除换为metaspace 元空间)等所有部分的模式 触发条件 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大则不会触发Minor GC而是转为触发full GC。老年代空间不够分配新的内存或永久代空间不足但只是JDK1.7有的这也是用元空间来取代永久代的原因可以减少Full GC的频率减少GC负担提升其效率由Eden区、From Space区向To Space区复制时对象大小大于To Space可用内存则把该对象转存到老年代且老年代的可用内存小于该对象大小。调用System.gc时系统建议执行Full GC但是不必然执行。
15.空间分配担保原则
年代空间分配担保机制来保证对象能够进入老年代YougGC时新生代有大量对象存活下来survivor 区放不下了这时老年代也放不下这些对象了使用空间分配担保原则。
执行YoungGC 前JVM会先检查老年代最大可用连续空间是否大于新生代所有对象的总大小。极端情况下新生代 YoungGC 后所有对象都存活下来了而 survivor 区又放不下所有对象都要进入老年代了。
如果老年代的可用连续空间是大于新生代所有对象的总大小的那就可以放心进行 YoungGC。如果老年代的内存大小是小于新生代对象总大小的那就有可能老年代空间不够放入新生代所有存活对象 JVM就会先检查 -XX:HandlePromotionFailure 参数是否允许担保失败如果允许就会判断老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小如果大于将尝试进行一次YoungGC尽管这次YoungGC是有风险的。如果小于或者 -XX:HandlePromotionFailure 参数不允许担保失败就会进行一次 Full GC。 在允许担保失败并尝试进行YoungGC后可能会出现三种情况 ① YoungGC后存活对象小于survivor大小此时存活对象进入survivor区中② YoungGC后存活对象大于survivor大小但是小于老年大可用空间大小此时直接进入老年代。③ YoungGC后存活对象大于survivor大小也大于老年大可用空间大小老年代也放不下这些对象了此时就会发生“Handle Promotion Failure”就触发了 Full GC。如果 Full GC后老年代还是没有足够的空间此时就会发生OOM内存溢出了。
16.类加载 类的生命周期 加载 通过类的全限定性类名获取该类的二进制流将该二进制流的静态存储结构转为方法区的运行时数据结构在堆中为该类生成一个class对象 连接 验证验证该class文件中的字节流信息复合虚拟机的要求不会威胁到jvm的安全准备为class对象的静态变量分配内存初始化其初始值解析该阶段主要完成符号引用转化为直接引用 初始化到了初始化阶段才开始执行类中定义的java代码初始化阶段是调用类构造器的过程使用卸载 类加载器 启动类加载器加载java核心类库无法被java程序直接引用扩展类加载器加载java的扩展库java虚拟机实现会提供一个扩展库目录该类加载器在扩展库目录里面查找并加载java类系统类加载器它根据java的类路径来加载类一般java应用的类都是通过它来加载的自定义类加载器由java语言实现继承自ClassLoader 双亲委派模型 收到类加载的请求首先不会尝试自己去加载将请求委派给父类加载器去加载只有父类加载器在自己的搜索范围类查找不到该类的时候子加载器才会尝试自己去加载该类。防止内存中出现多个相同的字节码如果没有双亲委派用户就可以自定义一个java.lang.String类无法保证类的唯一性打破双亲委派模型自定义类加载器继承ClassLoader类重写loadClass方法和findClass方法例子 JNDI引入线程上下文类加载器默认应用程序类加载器加载SPI代码Tomcat应用的类加载器优先自行加载应用目录下的class并不是先委派给父加载器加载不了才委派给父加载器 tomcat造了一堆自己的classloader目的 对于各个webapp中的class和lib需要相互隔离不能出现一个应用中加载的类库会影响另一个应用的情况对于许多应用需要有共享的lib以便不浪费资源与jvm一样的安全性问题使用单独的classLoader去装载tomcat自身的类库以免其他恶意或无意的破坏热部署 OSGI实现模块化热部署为每个模块都自定义了类加载器需要更换模块时模块与类加载器一起更换 JVM调优命令 jps显示指定系统内所有的HotSpot虚拟机进程jstat用于监视虚拟机运行时状态信息的命令它可以显示出虚拟机进程中的类装载内存垃圾收集JIT编译等运行数据jmap用于生成heap dump文件如果不使用这个命令还可以使用-xxHeapDumpOnOutOfMemoryError 参数来让虚拟机出现OOM的时候自动生成dump问价jhatjhat命令是与jmap搭配使用用来分析jmap生成的dumpjhat内置了一个微型的HTTP/HTML服务器生成dump的分析结果后可以再浏览器看在此要注意一般不会直接在服务器上进行分析因此jhat是一个耗时并且耗费硬件资源的过程一般把服务器生成的dump文件复制到本地或其他机器上进行分析jstack用于生成java虚拟机当前时刻的线程快照jstack来查看各个线程的调用堆栈就可以知道响应的线程到底在后台做什么事情或等待什么资源如果java程序崩溃生成core文件jstack工具可以用来获得core文件的java stack 和native stack的信息从而可以轻松地知道java程序是如何崩溃和在程序处发生问题。