烟台高新区规划国土建设局网站,郑州网站建设方案优化,网站被挂黑链怎么处理,百度平台订单查询JVM包含两个子系统和两个组件#xff0c;两个子系统为Class loader(类装载器)、Execution engine(执行引擎)#xff1b;两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。
方法区#xff1a;存储已被虚拟机加载的类元数据信息(元空间)
堆#xf… JVM包含两个子系统和两个组件两个子系统为Class loader(类装载器)、Execution engine(执行引擎)两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。
方法区存储已被虚拟机加载的类元数据信息(元空间)
堆存放对象实例几乎所有对象实例都在这里分配内存
Java栈(虚拟机栈)虚拟机栈描述的是Java方法执行的内存模型每个方法被执行的时候都会同时创建一个栈帧Stack Frame用于存储局部变量表、操作栈、动态链接、方法出口等信息
本地方法栈为虚拟机使用到的native方法服务
程序计数器当前线程所执行的字节码的行号指示器
深入理解Java虚拟机第二版 类加载器ClassLoader
类加载器分为四种前三种为虚拟机自带的加载器
启动类加载器Bootstrap负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class由C实现不是ClassLoader子类扩展类加载器Extension由Java语言实现负责加载java平台中扩展功能的一些jar包包$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包应用程序类加载器AppClassLoader由Java实现也叫系统类加载器负责加载classpath中指定的jar包及目录中class用户自定义加载器 Java.lang.ClassLoader的子类用户可以定制类的加载方式
工作过程
当AppClassLoader加载一个class时它首先不会自己去尝试加载这个类而是把类加载请求委派给父类加载器ExtClassLoader去完成当ExtClassLoader加载一个class时它首先也不会自己去尝试加载这个类而是把类加载请求委派给BootStrapClassLoader去完成如果BootStrapClassLoader加载失败例如在$JAVA_HOME/jre/lib里未查找到该class会使用ExtClassLoader来尝试加载若ExtClassLoader也加载失败则会使用AppClassLoader来加载如果AppClassLoader也加载失败则会报出异常ClassNotFoundException
以上流畅就是所谓的双亲委派模型如果一个类加载器收到了类加载的请求它首先不会自己去尝试加载这个类而是把请求委托给父加载器去完成依次向上请求
优点从安全性角度来看双亲委派模型可以防止内存中出现多份同样的字节码
执行引擎ExecutionEngine
Execution Engine执行引擎负责解释命令提交操作系统执行
本地方法栈Native Method Stack
它的具体做法是Native Method Stack中登记native方法在Execution Engine 执行时加载本地方法库。
本地接口Native Interface
本地接口的作用是融合不同的编程语言为 Java 所用它的初衷是融合 C/C程序Java 诞生的时候是 C/C横行的时候要想立足必须有调用 C/C程序蹭热度于是就在内存中专门开辟了一块区域处理标记为native的代码它的具体做法是 Native Method Stack中登记 native方法在Execution Engine 执行时加载native libraies。
目前该方法使用的越来越少了除非是与硬件有关的应用比如通过Java程序驱动打印机或者Java系统管理生产设备在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达比如可以使用 Socket通信也可以使用Web Service等。
PC寄存器Program Counter
即程序计数器
每个线程都有一个程序计数器是线程私有的就是一个指针指向方法区中的方法字节码用来存储指向下一条指令的地址就是将要执行的指令代码由执行引擎读取下一条指令是一个非常小的内存空间几乎可以忽略不记。
方法区Method Area
方法区被所有线程共享所有字段和方法字节码以及一些特殊方法如构造函数接口代码也在此定义。
简而言之所有定义的方法的信息都保存在该区域此区属于共享区间。
静态变量常量类信息(构造方法/接口定义)运行时常量池存在方法区中
但是实例变量存在堆内存中和方法区无关
栈Stack
栈也叫栈内存主管Java程序的运行是在线程创建时创建它的生命期是跟随线程的生命期线程结束栈内存也就释放对于栈来说不存在垃圾回收问题只要线程一结束该栈就Over生命周期和线程一致是线程私有的。8种基本类型的变量对象的引用变量实例方法都是在函数的栈内存中分配。
栈存储什么?
栈帧中主要保存3 类数据
本地变量Local Variables输入参数和输出参数以及方法内的变量。栈操作Operand Stack记录出栈、入栈的操作。栈帧数据Frame Data包括类文件、方法等等。
栈运行原理
栈中的数据都是以栈帧Stack Frame的格式存在栈帧是一个内存区块是一个数据集是一个有关方法(Method)和运行期数据的数据集当一个方法A被调用时就产生了一个栈帧 F1并被压入到栈中
A方法又调用了 B方法于是产生栈帧 F2 也被压入栈
B方法又调用了 C方法于是产生栈帧 F3 也被压入栈
……
执行完毕后先弹出F3栈帧再弹出F2栈帧再弹出F1栈帧……
遵循“先进后出”或者“后进先出”原则
每执行一个方法都会产生一个栈帧保存到栈(后进先出)的**顶部顶部栈就是当前的方法该方法执行完毕 后会自动将此栈帧出栈
常见问题栈溢出Exception in thread main java.lang.StackOverflowError
通常出现在递归调用时或者方法调用过深时。
堆Heap
堆栈方法区的关系 HotSpot是使用指针的方式来访问对象
Java堆中会存放访问类元数据的地址reference存储的就是对象的地址
Java栈保存对象的引用地址 堆保存引用地址对应的对象信息 方法区保存对象对应的class类型
Java7之前
一个JVM实例只存在一个堆内存堆内存大小是可以调节的。类加载器读取类文件后把类方法常量变量等信息放入堆内存中保存所有引用类型的真实信息以方便执行器执行。
堆内存逻辑分为三部分
Young Generation Space 年轻代 Young/New
Tenure Generation Space 老年代 Old/Tenure
Permanent Space 永久代 Perm
三种JVM
•Sun公司的HotSpot
•BEA公司的JRockit
•IBM公司的J9 VM
国内还有阿里巴巴的taobao JVM
其中JVM堆分为新生代和老年代 新生代
新生代是类的诞生、成长、消亡的区域一个类在这里产生应用最后被垃圾回收器回收结束生命。新生代又分为两部分 伊甸区Eden space和幸存者区Survivor pace 所有的类都是在伊甸区被new出来的。幸存区有两个 0区Survivor 0 space和1区Survivor 1 space。当伊甸园的空间用完时程序又需要创建对象JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC)将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存 0区。若幸存 0区也满了再对该区进行垃圾回收然后移动到 1 区。那如果1 区也满了呢再次垃圾回收满足条件后再移动到老年代。若老年代也满了那么这个时候将产生MajorGCFullGC进行老年代的内存清理。若老年代执行了Full GC之后发现依然无法进行对象的保存就会产生OOM异常—“OutOfMemoryError”
如果出现java.lang.OutOfMemoryError: Java heap space异常说明Java虚拟机的堆内存不够。原因有二
1Java虚拟机的堆内存设置不够可以通过参数-Xms(初始内存)、-Xmx(最大内存)来调整。
2代码中创建了大量大对象并且长时间不能被垃圾收集器收集存在被引用。
老年代
默认15次GC后还存在的对象就会放入老年代老年代对象比较稳定不会频繁的GC
永久代
永久代是一个常驻内存区域用于存放JDK自身所携带的ClassInterface的元数据也就是说它存储的是运行环境必须的类信息被装载进此区域的数据是不会被垃圾回收器回收的关闭 JVM 才会释放此区域所占用的内存。
如果出现 java.lang.OutOfMemoryError: PermGen space说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况都是程序启动需要加载大量的第三方jar包。例如在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载最终导致Perm区被占满。
Jdk1.6及之前 有永久代,常量池1.6在方法区
Jdk1.7 有永久代但已经逐步“去永久代”常量池1.7在堆
Jdk1.8及之后 无永久代常量池在元空间Metaspace
实际而言方法区Method Area和堆一样是各个线程共享的内存区域它用于存储虚拟机加载的
类信息普通常量静态常量编译器编译后的代码等等虽然JVM规范将方法区描述为堆的一个逻辑部分但它却还有一个别名叫做Non-Heap(非堆)目的就是要和堆分开。
对于HotSpot虚拟机很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” 但严格本质上说两者不同或者说使用永久代来实现方法区而已永久代是方法区相当于是一个接口interface的一个实现jdk1.7的版本中已经将原本放在永久代的字符串常量池移走。
为什么移除永久代
内存是启动时固定好的调优困难太小容易出现永久代溢出太大则容易导致老年代溢出字符串存在永久代中容易出现性能问题和内存溢出永久代会为 GC 带来不必要的复杂度并且回收效率偏低
常量池Constant Pool是方法区的一部分Class文件除了有类的版本、字段、方法、接口等描述信息外还有一项信息就是常量池这部分内容将在类加载后进入方法区的运行时常量池中存放。
元空间并不在虚拟机中而是使用本地内存默认情况下元空间的大小仅受本地内存限制可以通过以下参数调优 -XX:MetaspaceSize初始空间大小达到该值就会触发垃圾收集进行类型卸载同时GC会对该值进行调整如果释放了大量的空间就适当降低该值如果释放了很少的空间那么在不超过MaxMetaspaceSize时适当提高该值。 -XX:MaxMetaspaceSize最大空间默认是没有限制的。
除了上面两个指定大小的选项以外还有两个与 GC 相关的属性
-XX:MinMetaspaceFreeRatio在GC之后最小的Metaspace剩余空间容量的百分比减少为分配空间所导致的垃圾收集 -XX:MaxMetaspaceFreeRatio在GC之后最大的Metaspace剩余空间容量的百分比减少为释放空间所导致的垃圾收集
堆参数调优
常用JVM参数
参数备注-Xms初始堆大小。只要启动就占用的堆大小默认是内存的1/64-Xmx最大堆大小。默认是内存的1/4-Xmn新生区堆大小-XX:PrintGCDetails输出详细的GC处理日志Java代码查看jvm堆的默认值大小
Runtime.getRuntime().maxMemory() // 堆的最大值默认是内存的1/4
Runtime.getRuntime().totalMemory() // 堆的当前总大小默认是内存的1/64设置JVM参数
程序运行时可以给该程序设置jvm参数不同的工具设置方式不同。
命令行
java -Xmx50m -Xms10m HeapDemoeclipse、IDEA 在 Run Configuration 的 VM Options 中设置。
查看堆内存详情
public class Demo2 {public static void main(String[] args) {System.out.print(最大堆大小);System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 M);System.out.print(当前堆大小);System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 M);System.out.println();byte[] b null;for (int i 0; i 10; i) {b new byte[1 * 1024 * 1024];}}
}执行前配置参数-Xmx50m -Xms30m -XX:PrintGCDetails
执行看到如下信息 新生代和老年代的堆大小之和是Runtime.getRuntime().totalMemory()
开发中eclipse使用MAT工具、IDEA分析Dump文件来定位OOM位置。
GC垃圾回收
JVM垃圾判定算法
引用计数法(Reference-Counting)可达性分析算法根搜索算法
GC垃圾回收算法
复制算法(Copying)标记清除(Mark-Sweep)标记压缩(Mark-Compact)又称标记整理分代回收算法(Generational-Collection)
垃圾判定
引用计数法(Reference-Counting)
引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收
给对象中添加一个引用计数器每当有一个地方引用它时计数器值就加1当引用失效时计数器值就减1任何时刻计数器为0的对象就是不可能再被使用的
优点
简单高效现在的objective-c、python等用的就是这种算法。
缺点
引用和去引用伴随着加减算法影响性能很难处理循环引用相互引用的两个对象则无法释放
因此目前主流的Java虚拟机都摒弃掉了这种算法
可达性分析算法
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点从这些节点开始向下搜索节点所走过的路径称为引用链当一个对象到 GC Roots 没有任何引用链相连的话则证明此对象是不可用的。 在Java中可以作为GC Roots的对象包括下面几种
虚拟机栈栈帧中的本地变量表中的引用对象。方法区中的类静态属性引用的对象。方法区中的常量引用的对象。本地方法栈中JNINative方法的引用对象
垃圾回收算法
复制算法(Copying)
该算法将内存平均分成两部分然后每次只使用其中的一部分当这部分内存满的时候将内存中所有存活的对象复制到另一个内存中然后将之前的内存清空只使用这部分内存循环下去。 优点
实现简单不产生内存碎片
缺点
将内存缩小为原来的一半浪费了一半的内存空间代价太高如果不想浪费一半的空间就需要有额外的空间进行分配担保以应对被使用的内存中所有对象都100%存活的极端情况所以在老年代一般不能直接选用这种算法。如果对象的存活率很高我们可以极端一点假设是100%存活那么我们需要将所有对象都复制一遍并将所有引用地址重置一遍。复制这一工作所花费的时间在对象存活率达到一定程度时将会变的不可忽视。 所以从以上描述不难看出复制算法要想使用最起码对象的存活率要非常低才行而且最重要的是我们必须要克服50%内存的浪费。
年轻代中使用的是Minor GC这种GC算法采用的是复制算法(Copying)。
HotSpot JVM把年轻代分为了三部分1个Eden区和2个Survivor区分别叫from和to。默认比例为8:1:1,一般情况下新创建的对象都会被分配到Eden区。因为年轻代中的对象基本都是朝生夕死的(90%以上)所以在年轻代的垃圾回收算法使用的是复制算法。
在GC开始的时候对象只会存在于Eden区和名为“From”的Survivor区Survivor区“To”是空的。紧接着进行GCEden区中所有存活的对象都会被复制到“To”而在“From”区中仍存活的对象会根据他们的年龄值来决定去向。对象在Survivor区中每熬过一次Minor GC年龄就会增加1岁。年龄达到一定值(年龄阈值可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中没有达到阈值的对象会被复制到“To”区域。经过这次GC后Eden区和From区已经被清空。这个时候“From”和“To”会交换他们的角色也就是新的“To”就是上次GC前的“From”新的“From”就是上次GC前的“To”。不管怎样都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程直到“To”区被填满“To”区被填满之后会将所有对象移动到年老代中。 因为Eden区对象一般存活率较低一般的使用两块10%的内存作为空闲和活动区间而另外80%的内存是用来给新建对象分配内存的。一旦发生GC将10%的from活动区间与另外80%中存活的eden对象转移到10%的to空闲区间接下来将之前90%的内存全部释放以此类推。
标记清除(Mark-Sweep)
“标记-清除”(Mark Sweep)算法是几种GC算法中最基础的算法是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到的。正如名字一样算法分为2个阶段
标记出需要回收的对象使用的标记算法均为可达性分析算法。回收被标记的对象。缺点
效率问题两次遍历空间问题标记清除后会产生大量不连续的碎片。JVM就不得不维持一个内存的空闲列表这又是一种开销。而且在分配数组对象的时候寻找连续的内存空间会不太好找。
标记压缩(Mark-Compact)
标记-整理法是标记-清除法的一个改进版。同样在标记阶段该算法也将所有对象标记为存活和死亡两种状态不同的是在第二个阶段该算法并没有直接对死亡的对象进行清理而是通过所有存活对像都向一端移动然后直接清除边界以外的内存。 优点
标记/整理算法不仅可以弥补标记/清除算法当中内存区域分散的缺点也消除了复制算法当中内存减半的高额代价。
缺点
如果存活的对象过多整理阶段将会执行较多复制操作导致算法效率降低。
老年代一般是由标记清除或者是标记清除与标记整理的混合实现。 分代回收算法(Generational-Collection)
从简单情况的时间复杂度来看
内存效率复制算法 标记清除算法 标记整理算法 内存整齐度复制算法 标记整理算法 标记清除算法 内存利用率标记整理算法 标记清除算法 复制算法
可以看出效率上来说复制算法是当之无愧的老大但是却浪费了太多内存而为了尽量兼顾上面所提到的三个指标标记整理算法相对来说更平滑一些但效率上依然不尽如人意它比复制算法多了一个标记的阶段又比标记清除多了一个整理内存的过程
分代回收算法实际上是把复制算法和标记整理法的结合并不是真正一个新的算法一般分为老年代和年轻代老年代就是很少垃圾需要进行回收的新生代就是有很多的内存空间需要回收所以不同代就采用不同的回收算法以此来达到高效的回收算法。
年轻代(Young Gen)
年轻代特点是区域相对老年代较小对像存活率低。
这种情况复制算法的回收整理速度是最快的。复制算法的效率只和当前存活对像大小有关因而很适用于年轻代的回收。而复制算法内存利用率不高的问题通过hotspot中的两个survivor的设计得到缓解。
老年代(Tenure Gen)
老年代的特点是区域较大对像存活率高。
这种情况存在大量存活率高的对像复制算法明显变得不合适。一般是由标记清除或者是标记清除与标记整理的混合实现。
垃圾收集器
垃圾回收的具体实现Serial收集器、 Parallel收集器、 CMS收集器、G1收集器。