电子商城网站开发 pdf,在网站做推广要钱吗,php 网站伪静态,设计类公司网站堆的核心概述 一个JVM实例只存在一个堆内存#xff0c;堆也是内存管理的核心区域。 Java堆区在JVM启动的时候即被创建#xff0c;其空间大小也就确定了。是JVM管理的最大一块内存空间。 堆内存的大小是可以调节的。 《JVM虚拟机规范》规定#xff0c;堆可以处于物理上不连…堆的核心概述 一个JVM实例只存在一个堆内存堆也是内存管理的核心区域。 Java堆区在JVM启动的时候即被创建其空间大小也就确定了。是JVM管理的最大一块内存空间。 堆内存的大小是可以调节的。 《JVM虚拟机规范》规定堆可以处于物理上不连续的内存空间中但在逻辑上它应该被视为连续的。 所有的线程共享Java堆在这里还可以划分线程私有的缓冲区Thread Local Allocation BufferTLAB。 在《Java虚拟机规范》中对Java堆的描述所有的对象实例以及数组都应当在运行时分配在堆上。 数组和对象可能永远不会存储在栈上因为栈帧中保存引用这个引用指向对象或者数组在堆中的位置。 在方法结束后堆中的对象不会马上被移除仅仅在垃圾收集的时候才会被移除。 堆是GCGarbage Collection垃圾收集器执行垃圾回收的重点区域。 内存细分
现代垃圾收集器大部分都基于分代收集理论设计堆空间细分为 Java7及之前堆内存逻辑上分为三个部分新生区养老区永久区 Young Generation Space 新生区 Young/New 又被划分为Eden区和Survivor区 Tenure Generation Space 养老区 Old/Tenure Permanent Space 永久代 Perm Java8及之后堆内存逻辑上分为三个部分新生区养老区元空间 Young Generation Space 新生区 Young/New 又被划分为Eden区和Survivor区 Tenure Generation Space 养老区 Old/Tenure Meta Space 元空间 Meta 约定 新生区新生代年轻代 养老区老年区老年代 永久区永久代 设置堆内存大小与OOM Java堆区用于存储Java对象实例那么堆的大小在JVM启动时就已经定好了大家可以通过选项“-Xmx”和“-Xms”来进行设置。 “-Xms”用于表示堆区的起始内存等价于-XX:InitialHeapSize “-Xmx”用于表示堆区的最大内存等价于-XX:MaxHeapSize 一旦堆区中的内存大小超过“-Xmx”所指定的最大内存时将会抛出OutOfMemoryError异常 通常会 将-Xms和-Xmx两个参数配置相同的值其目的是为了能够在Java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小从而提高性能。 默认情况下初始内存大小 物理电脑内存大小 / 64 最大内存大小物理电脑内存大小 / 4
年轻代与老年代 存储在JVM中的Java对象可以被划分为两类 一类是生命周期较短的瞬时对象这类对象的创建和消亡都非常迅速 另外一类对象的生命周期却非常长在某些极端的情况下还能够与JVM的生命周期保持一致。 Java堆区进一步细分的话可以划分为年轻代YoungGen和老年代OldGen 其中年轻代又可以划分为Eden空间 Survivor0空间和Survivor1空间有时也叫做from区to区 下面这参数开发中一般不会调 配置新生代与老年代在堆结构的占比。 默认-XX:NewRatio2表示新生代占1老年代占2新生代占整个堆的1/3。 可以修改-XX:NewRatio4表示新生代占1老年代占4新生代占整个堆的1/5。 在HotSpot中Eden空间和另外两个Survivor空间缺省所占的比例是8:1:1 可以 通过选项“-XX:SurvivorRation”调整这个空间比例 几乎所有的Java对象都是在Eden区被new出来的。 绝大部分的Java对象的销毁都在新生代进行了 可以使用“-Xmn”设置新生代最大内存大小
图解对象分配过程
概述 new的对象先放在Eden区此区有大小限制。 当Eden的空间填满时程序又需要创建对象JVM的垃圾回收器将对Eden进行垃圾回收Minor GC将Eden区中的不在被其它对象所引用 的对象进行销毁再加载虚表的对象放到Eden区。 然后将Eden中的剩余对象移动到Survivor0区。 如果再次触发垃圾回收此时上次幸存下来的放到Survivor0区的如果没有回收就会放到Survivor1区。 如果再次经历垃圾回收此时会重新放回Survivor0区接着再去Survivor1区。 啥时候能去养老区呢可以设置次数默认是15次。 可以设置参数-XX:MaxTenuringThresholdN进行设置。 在养老区相对悠闲。当养老区内存不足时再次触发GCMajor GC进行养老区的内存清理。 若养老区执行了Major GC之后发现依然无法进行对象的保存就会 产生OOM异常。 java.lang.OutOfMemoryErrorJava heap space 结 针对Survivor s0,s1区的总结复制之后有交换谁空谁是to。 关于垃圾回收频繁在新生区收集很少在养老区收集几乎不在永久区/元空间收集。 常用调优工具 JDK命令行 Eclipse:Memory Analyzer Tool Jconsole VisualVM Jprofiler Java Flight Recorder GCViewer GC Easy
Minor GCMajor GCFull GC
JVM在进行GC时并非每次都对上面三个内存新生代老年代方法区区域一起回收的大部分时候回收的都是指新生代。
针对HotSpot VM的实现它里面的GC按照回收区域又分为两大种类型一种是不分收集Partial GC,一种是整堆收集Full GC 部分收集不是完整收集整个Java堆的垃圾收集。其中又分为 新生代收集Minor GC / Young GC只是新生代的垃圾收集。 老年代收集Major GC / Old GC只是老年代的垃圾收集。 目前只有GMS GC会有单独收集老年代的行为。 注意很多时候Major GC会和Full GC混淆使用需要具体分辨是老年代回收还是整堆回收。 混合收集Mixed GC收集整个新生代以及部分老年代的垃圾收集。 目前只有G1 GC会有这种行为 整堆收集Full GC收集整个Java堆和方法区的垃圾收集。
最简单的分代式GC策略的触发条件 年轻代GCMinor GC触发机制 当年轻代空间不足时就会触发Minor GC这里的年轻代满指的是Eden代满Survivor满不会引发GC。每次Minor GC会清理年轻代的内存。 因为Java对象大多都具备朝生夕死的特性所以Minor GC非常频繁一般回收速度也比较快。这一定义既清晰又易于理解。 Minor GC会引发STW暂停其它用户的线程等垃圾回收结束用户线程才恢复运行。 老年代GCMajor GC/Full GC触发机制 指发生在老年代的GC对象从老年代消失时我们 说 “Major GC”或”Full GC“发生了。 出现了Major GC经常会伴随至少一次的Minor GC但非绝对的在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程。 也就是在老年代空间不足时会先尝试触发Minor GC。如果之后空间还不足则触发Major GC. Major GC的速度一般会比Minor GC慢10倍以上STW的时间更长。 如果Major GC后内存还不足就报OOM了 Major GC的速度一般会比Minor GC慢10倍以上。 Full GC触发机制 触发Full GC执行的情况有如下五种 调用System.gc()时系统建议执行Full GC但是不必然执行。 老年代空间不足 方法区空间不足 通过Minor GC后进入老年代的平均大小大于老年代的可用内存 由Eden区Survivor space0From Space区向Survivor space1To Space区复制时对象大小大于To Space可用内存则把该对象转存到老年代且老年代的可用内存小于该对象大小 说明full gc是开发或调优中尽量要避免的。这样暂时时间会短一些。
堆空间分代思想
为什么需要把Java堆分代不分代就不能正常工作了吗 经研究不同对象的生命周期不同。70%-99%的对象是临时对象。 新生代有Eden两块大小相同的Survivor又称为from/tos0/s1构成to为空 老年代存放新生代中经历多次GC仍然存活的对象。 其实不分代完全可以分代的唯一理由就是优化GC性能。如果没有分代那所有的对象都在一块就如同把一个学校的人都关在一个教室。GC的时候要找到哪些对象没用这样就会对堆的所有区域进行扫描。而很多对象都是朝生夕死的如果分代的话把新创建的对象放到某一地方当GC的时候先把这块存储“朝期生岁死”对象的区域进行回收这样就会腾出很大的空间出来
内存分配策略
如果对象在Eden出生并经过第一次MinorGC后仍然存活并且能被Survivor容纳的话将被移动到Survivor空间中并将对象年龄设为1。对象在Survivor区中每熬过一次Minor GC年龄就增加1岁当它的年龄增加到一定程度默认为15岁其实每个JVM每个GC都有所不同时就会被晋升到老年代中。
对象晋升老年代的年龄阀值可以通过选项-XXMaxTenuringThreshold来设置。
针对不同年龄段的对象分配原则如下所示 优先分配到Eden 大对象直接分配到老年代 尽量避免程序中出现过多的大对象 长期存活的对象分配到老年代 动态对象年龄判断 如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半年龄大于或等于该年龄的对象可以直接进入老年代无须等到MaxTenuringThreshold中要求的年龄。 空间分配担保 -XX:HandlePromotionFailure
为对象分配内存TLAB
为什么有TLABTHread Local Allocation Buffer? 堆区是线程共享区域任何线程都可以访问到堆区中的共享数据 由于对象实例的创建在JVM中非常频繁因此在并发环境下从堆区中划分内存空间是线程不安全的 为避免多个线程操作同一地址需要使用加锁等机制进而影响分配速度。
什么是TLAB 从内存模型而不是垃圾收集的角度对Eden区域继续进行划分JVM为每个线程分配了一个私有缓存区域它包含在Eden空间内。 多线程同时分配内存时使用 TLAB可以避免一系列的非线程安全问题同时还能够提升内存分配的吞吐量因此我们可以将这种内存分配方式称之为快速分配策略。 TLAB的再说明 尽管不是所有的对象实例都能够在TLAB中成功分配内存但JVM确实是将TLAB作为内存分配的首选。 在程序中开发人员可以通过选项“-XX:UseTLAB”设置是否开启TLAB空间。 默认情况下TLAB空间的内存非常小仅占有整个Eden空间的1%当然我们可以通过选项“-XX:TLABWasteTargetPercent”设置TLAB空间所占用Eden空间的百分比大小。 一旦对象在TLAB空间分配内存失败时JVM就会尝试着通过使用加锁机制确保数据操作的原子性从而直接在Eden空间中分配内存。 小结堆空间的参数设置 -XX:PrintFlagsInitial查看所有的参数的默认初始值 -XX:PrintFlagsFinal查看所有的参数的最终值可能会存在修改 不再是初始值 -XmS初始堆空间内存默认为物理内存的1/64 -XmX最大堆空间内存默认为物理内存的1/4 Xmn设置新生代的大小。初始值及最大值 -XXNewRatio配置新生代与老年代在堆结构的占比 -XX:SurvivorRatio设置新生代中Eden和s0/s1空间的比例 -XX:MaxTenuringThreshold设置新生代垃圾的最大年龄 -XX:PrintGCDetails输出详细的GC处理日志 打印GC简要信息 -XX:PrintGC -verbose:gc -XX:HandlePromotionFailure是否设置空间分配担保 在发送Minor GC之前虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。 如果大于则此次Minor GC是安全的。 如果小于则虚拟机会查看-XX:HandlePromotionFailure设置值是否允许担保失败。 如果HandlePromotionFailuretrue那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小。 如果 大于则尝试进行一次Minor GC但这次Minor GC依然是有风险的 如果小于则改为进行一次Full GC。 如果HandlePromotionfalse则改为进行一次Full GC。
在JDK6Update24之后HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略观察openJDK中的源码变化虽然源码中还定义了 HandlePromotionFailure参数但是在代码中已经不会再使用它。JDK6Update24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次普升的平均大小就会进行MinorGc否则将进行FullGc。
堆是分配对象的唯一
在Java虚拟机中对象是在Java堆中中分配 的这是普遍的常识。但是有一种特殊情况那就是如果经过逃逸分析Escape Analysis后发现一个对象并没有逃逸出方法的话那么就可能被优化成栈上分配。这样也就无须在堆上分配也无须进行垃圾回收。这也就是常见的堆外存储技术。
逃逸分析概述 如何将堆上的对象分配到栈需要使用逃逸分析手段。 这是一种可以有效减少Java程序中同步负载和内存堆分配压力的夸函数全局数据流分析算法。 通过逃逸算法分析Java HotSpot编译器能够分析出一个新的对象的引用的使用范围从而决定是否将要这个对象分配到堆上。 逃逸分析的基本行为就是分析对象动态作用域 当一个对象在方法中被定义后对象只在方法内部使用则认为没有发生逃逸。 当一个对象在方法中被定义后它被 外部方法所引用则认为发生逃逸。例如作为调佣参数传递到其他地方中。 参数设置 在JDK 6u23版本之后HotSpot中默认就已经开启了逃逸分析。 如果使用的是较早的版本开发人员则可以通过 选项“-XX:DoEscapeAnalysis显式开启逃逸分析 通过选项“-XX:PrintEscapeAnalysis查看逃逸分析的筛选结果。 结论 开发中 能使用局部变量的就不要使用在方法外定义。 逃逸分析代码优化
使用逃逸分析编译器可以对代码做如下优化 栈上分配 将堆分配转化为栈分配。如果一个对象在子程序中被分配要使指向该对象的指针永远不会逃逸对象可能是栈分配的候选而不是堆分配。 同步省略 如果一个对象被发现只能从一个线程被访问到那么对于这个对象的操作可以不考虑同步。 分离对象或标量替换 有的对象可能不需要作为一个连续的内存结构存在也可以被访问到那么对象的部分或全部可以不存储在内存而是存储在CPU寄存器中。
代码优化之栈上分配 JIT编译器在编译期间 根据逃逸分析的结果发现如果一个对象并没有逃逸出方法的话就可能被优化成栈上分配 。分配完成后继续在调佣栈内执行最后线程结束栈空间被回收局部变量对象也被回收。这样就无须进行垃圾回收了。 常见的栈上分配的场景 变量赋值 方法返回值 实例引用传递
代码优化之同步省略消除 线程同步的代价是相当高的同步的后果是降低并发性和性能。 在动态编译同步快的时候JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。如果没有那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫同步省略也叫锁消除
代码优化之标量替换
标量Scale是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。相对的那些还可以再分解的数据叫做聚合量AggregateJava中的对象就是聚合量因为他可以分解成其他聚合量和标量。
在JIT阶段如果经过逃逸分析发现一个对象不会被外界访问的话那么经过JIT优化就会把这个对象拆解成若干个其中包含的若干个变量来代替。这个代替就是标量替换。 标量替换参数设置 参数-XX:EliminateAllocations开启了标量替换默认打开允许将对象分配在栈上。
小结 逃逸分析并不成熟 其根本原因就是无法保证逃逸分析的性能消耗一定能高于他的消耗。虽然经过逃逸分析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的这其实也是一个相对耗时的过程 年轻代是对象的诞生、成长、消亡的区域一个对象在这里产生、应用最后被垃圾回收器收集、结束生命。 老年代放置长生命周期的对象通常都是从Survivor区域筛选拷贝过来的 JaVa对象。当然也有特殊情况我们知道普通的对象会被分配在TLAB上如果对象较大JVM会试图直接分配在Eden其他位置上如果对象太大完全无法在新生代找到足够长的连续空闲空间JVM就会直接分配到老年代 当GC只发生在年轻代中回收年轻代对象的行为被称为Minor GC。当GC 发生在老年代时则被称为Maior GC或者Full GC。一般的MinorGC的发生频率要比MaiorGC高很多即老年代中垃圾回收发生的频率将大大低于年轻代。