JVM 主要由 ClassLoader
和 执行引擎
两子系统组成.html
任何一个Java类的main方法运行都会建立一个JVM实例, 当main函数结束时, JVM实例也就结束了. JVM实例启动时默认启动几个守护线程, 好比: 垃圾回收的线程, 而 main 方法的执行是在一个单独的非守护线程中执行的.只要母线程结束, 子线程就自动销毁, 只要非守护main 线程结束JVM实例就销毁了.java
JVM的工做原理以下:git
类生命周期 github
类: 须要由加载它的类加载器和这个类自己共同保证其在JVM中的惟一性算法
加载编程
经过类的全路径名获取类的二进制字节流,将类的静态内容和对象信息加载进方法区,在堆中建立对象,做为方法区数据的访问入口.数组
具体是将类的.class
文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,而后在堆区建立一个java.lang.Class
对象,做为这个类封装在方法区内的数据结构的入口.安全
类的加载最终是在堆区内的Class对象,Class对象封装了类在方法区内的数据结构,而且向开发者提供了访问方法区内的数据结构的接口.数据结构
怎样加载一个类:多线程
Class.forName()
方法动态加载ClassLoader.loadClass()
方式动态加载,如ClassLoader.getSystemClassLoader().loadClass("org.luvx.User")
一个类被加载,当且仅当其某个静态成员(静态方法等、构造器)被调用时发生,加载一个类时,其内部类不会同时被加载。
验证
检查Class文件数据的正确性,是否符合当前虚拟机的要求 ,是否会危害JVM的安全等,是类加载过程当中最复杂耗时的过程.
细分为如下过程:
准备
正式为类的静态内容分配内存并设置变量初始值
- 进行内存分配的仅是静态变量,不包括对象变量,对象变量在对象实例化时随着对象分配在堆内存中.
- 设置初始值并非是什么就是什么,而是设置对应类型的初始值,如定义
static int num = 12
,此时设置为0,12是在上图初始化阶段设置,但static final
修饰除外,直接就是12
解析
将常量池中的符号引用替换为直接引用,主要针对类或接口、字段、类方法、接口方法四类符号引用进行
符号引用不必定要已经加载到内存,而直接引用一定存在于存中 关于符号引用, 查下如下代码的字节码:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 符号引用 public class Test { public static void main() { String s="adc"; System.out.println("s=" + s); } } // 直接�引用 public class Test { public static void main() { System.out.println("s=" + "abc"); } } |
初始化
类加载的最后阶段,对静态内容进行初始化操做
Java类初始化顺序:
父类静态变量->父类静态代码块->子类静态代码块->父类非静态变量->父类非静态代码块->父类构造函数->子类非静态变量->子类非静态代码块->子类构造函数
不会加载类的情形:
Class.forName()
加载类时,指定参数initialize
为false
ClassLoader
的loadClass()
方法加载类Java中的类都是在程序运行期间加载的,虽然会下降性能,但这种动态加载机制增长了灵活性,如面向接口编程中,只有运行时才能知道具体的类,能够自定义类加载器,动态加载指定的二进制数据建立对象.
JVM规范中并无约束类加载时机,但约束了5种状况需对类进行初始化操做,其以前的操做天然就须要完成.
java.lang.reflect
包的方法对类进行反射调用main()
方法的类java.lang.invoke.MethodHandle
的解析结果对应的类没有初始化,则须要初始化本身写的两个不一样的类是被同一个类加载器加载的吗?为何
类加载器的做用就是从字节码建立一个类,并负责加载 Java 应用所需的资源.
只有当一个类要使用的时候,类加载器才会加载这个类并初始化
rt.jar
java.lang.ClassLoader
实现自定义类加载器.User user = new User()
实质就是User user = Class.forName("org.luvx.User", false, this.class.getClassLoader()).newInstance();
双亲委托模型
工做过程为:一个类收到类加载的请求,首先不会本身去尝试加载,而是委派为父加载器去加载,只有当父类反馈没法加载时,才会尝试本身去加载. 若是全部加载器均加载失败, 则会抛出ClassNotFoundException
异常.
意义: 能够保证java的一些重要类如Object在各类类加载器加载下都是同一个类,由于最终都是由启动类加载器加载,保证的类的惟一性.
存在的问题:模型自己决定的,例如基础类要掉回用户代码 怎么解决了:线程上下文类加载器
开发者能够继承java.lang.ClassLoader
并重写findClass()
方法便可建立自定义类加载器.
一个类的类型是类自己和加载该类的加载器一块儿肯定的
NoClassDefFoundError
NoSuchMethodError
ClassCastException:同一个类若是被不一样的加载器加载,那他们就不是同一个类,也没法将一个类强转为另外一个类,会报类转换异常,这也是ClassLoader隔离
问题.
java 的对象分配策略 在Eden中, 大对象直接进入老年代, 长期存活的对象进入老年代, 动态年龄分配, 空间分配担保
区域 | 做用 | 共享性 | 存储内容 |
---|---|---|---|
堆内存 | 存放对象实例,能够细分为新生代和老年代 | √ | new出来的对象(属性,方法的地址(指向方法区)) |
方法区 | 内有运行时常量池,也有人称之为永久代 | √ | 常量,static变量,类信息(属性,方法) |
运行时常量池 | 方法区的一部分 | √ | 运行时常量池(各类字面量和符号引用) |
程序计数器 | 比较小的内存区域,指示当前线程所执行的字节码的位置 | × | 正在执行的VM字节码指令地址(Java方法,native方法时为空) |
VM栈 | 记录方法调用 | × | 局部变量表,对象的引用指针 |
本地方法栈 | 执行Native方法时使用 | × | - |
一块较小的内存空间, 属于线程私有.
字节码解释器工做时就是经过改变这个计数器的值来选取下一条须要执行的字节码指令, 分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成. 若是线程正在执行一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
若是是Native方法,则计数器为空;多线程时, 存在多个程序计数器.
此内存区域是惟一一个在Java虚拟机规范中没有规定任何OutOfMemoryError状况的区域.
线程私有, 生命周期与线程相同
虚拟机栈描述的是Java方法执行的内存模型:每一个方法被执行的时候都会同时建立一个栈帧(Stack Frame)用于存储局部变量表、操做栈、动态连接、方法出口等信息.
每个方法被调用直至执行完成的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程.
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时该方法对应的须要在栈帧中分配多大的局部变量空间是彻底肯定的.
对这个区域规定了两种异常情况: 若是线程请求的栈深度大于虚拟机所容许的深度, 将抛出StackOverflowError
异常; 若是虚拟机栈能够动态扩展(当前大部分的Java虚拟机均可动态扩展, 只不过Java虚拟机规范中也容许固定长度的虚拟机栈), 当扩展时没法申请到足够的内存时会抛出OutOfMemoryError
异常.
栈帧: 一个栈帧随着一个方法的调用开始而建立,这个方法调用完成而销毁.栈帧内存放着方法中的局部变量,操做数栈等数据
Java堆是被全部线程共享的一块内存区域, 在虚拟机启动时建立.此内存区域的惟一目的就是存放对象实例, 几乎全部的对象实例都在这里分配内存.
Java堆是垃圾收集器管理的主要区域, 所以不少时候也被称作"GC堆" 多采用分代收集策略,因此细分为新生代和老年代,再细分可分为Eden空间,From Survivor空间,To Survivor空间,在GC的复制算法中起着重要做用,HotSpot VM默认的Eden和Survivor大小比例为8:1
堆内存能够是物理上不连续的内存空间, 逻辑上连续便可,
在堆中没有内存完成实例分配, 而且堆也没法再扩展时, 将会抛出OutOfMemoryError
异常
与Java堆同样, 是各个线程共享的内存区域, 它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,有时也被称为永久代(PermGen)
Java虚拟机规范对这个区域的限制很是宽松, 除了和Java堆同样不须要连续的内存和能够选择固定大小或者可扩展外, 还能够选择不实现垃圾收集
这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载
当方法区没法知足内存分配需求时, 将抛出OutOfMemoryError
异常
是方法区的一部分,存放编译期生成的各类字面量和符号引用,当JVM运行的时候会将这些常量池的信息加载进方法区.
当方法区没法知足内存分配需求时,抛出OutOfMemoryError
虚拟机栈为虚拟机执行Java方法(也就是字节码)服务, 而本地方法栈则是为虚拟机使用到的Native方法服务
异常抛出类型和JVM栈相同
不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,可是这部份内存也被频繁的使用.
也可能致使OutOfMemoryError
异常出现
Exception in thread "main": java.lang.OutOfMemoryError: Java heap space
缘由: 对象不能被分配到堆内存中
Exception in thread "main": java.lang.OutOfMemoryError: PermGen space
缘由: 类或者方法不能被加载到永久代.它可能出如今一个程序加载不少类的时候, 好比引用了不少第三方的库;
Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit
缘由: 建立的数组大于堆内存的空间
Exception in thread "main": java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?
缘由: 分配本地分配失败.JNI、本地库或者Java虚拟机都会从本地堆中分配内存空间.
Exception in thread "main": java.lang.OutOfMemoryError: <reason> <stack trace>(Native method)
缘由: 一样是本地方法内存分配失败, 只不过是JNI或者本地方法或者Java虚拟机发现
java.lang.OutOfMemoryError: unable to create new native thread
缘由: 建立了太多的线程,而能建立的线程数是有限制的,致使了异常的发生
工具 | 做用 |
---|---|
jps | 进程状态工具,查看正在运行的JVM进程 |
jstat | 统计信息监视工具,实时显示JVM进程中类装载、内存、垃圾收集、JIT编译等数据 |
jinfo | 配置信息工具,查询当前运行着的JVM属性和参数的值 |
jmap | 内存映射工具,生成VM的内存转储快照 |
jhat | 堆转储快照分析工具,分析使用jmap生成的dump文件 |
jstack | 堆栈跟踪工具,生成当前JVM的全部线程快照,线程快照是虚拟机每一条线程正在执行的方法,目的是定位线程出现长时间停顿的缘由. |
jconsole | jvm监视管理控制台,图像化显示堆栈等使用状况,能够手动进行GC,很是实用 |
jcmd |
JVM参数:
工具 | 做用 |
---|---|
-Xmx | 最大堆内存 |
-Xms | 最小堆内存, 一般设置成跟最大堆内存同样,减小GC |
-Xmn | 设置年轻代大小,官方推荐设置为堆的3/8 |
-Xss | 指定线程的最大栈空间, 此参数决定了java函数调用的深度, 值越大调用深度越深, 若值过小则容易出栈溢出错误(StackOverflowError) |
-XX:PermSize | 指定方法区(永久区)的初始值,默认是物理内存的1/64, 在Java8永久区移除, 代之的是元数据区, 由-XX:MetaspaceSize指定 |
-XX:MaxPermSize | 指定方法区的最大值, 默认是物理内存的1/4, 在java8中由-XX:MaxMetaspaceSize指定元数据区的大小 |
-XX:NewRatio=n | 年老代与年轻代的比值,-XX:NewRatio=2, 表示年老代与年轻代的比值为2:1 |
-XX:SurvivorRatio=n | Eden区与一个Survivor区的大小比值,-XX:SurvivorRatio=8表示Eden区与两个Survivor区的大小比值是8:1:1,由于Survivor区有两个(from, to) |
GC的工做内容就是就是回收内存,包括哪些内存须要回收,何时回收,怎么回收等3件工做.
堆内存也被称为GC堆,是由于GC的主要进行场所就是堆内存,方法区是堆内存的一部分,一样也能够GC的对象,
但Java虚拟机规范不要求虚拟机在方法区实现GC,并且在方法区进行GC的"性价比"通常都比较低,这和方法区被称为永久代有着相同的缘由.
在GC进行前首先要肯定的就是对象是否还活着
(是否还在直接或间接的被使用中)
判断对象的使用常有如下2种策略:
引用计数算法
存储对特定对象的全部引用数,也就是说,当应用程序建立引用以及引用超出范围时,JVM必须适当增减引用数.当某对象的引用数为0时,即可以进行垃圾回收. 优势: 实现简单、效率高 缺点: 很难解决对象之间相互引用问题
可达性分析算法
经过一系列的称为"GC Roots"的对象做为起始点,从这些节点开始向下搜索,搜索所走的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的.
可做为GC Roots的对象
包括:
标记-清除算法
Mark-Sweep,适用于老年代,最基础的算法,后续的算法都是基于这种思想改进而来,
标记或清除过程的效率都不高,产生大量不连续的内存碎片,在分配大对象时候因没法找到符合的连续空间而再次进行GC
标记-整理-清除算法
在标记-清除算法的标记的基础上,将不被回收的对象向同一端移动,而后清理到边界外的内存,解决了内存碎片
复制算法
为解决标记清除算法效率和形成的不连续碎片问题而生,适用于对象存活率低的新生代
将内存分为一块较大的Eden空间和两块较小的Survivor,每次使用Eden和其中一块Survivor,回收时,将Eden和Survivor中存活的对象一次性地拷贝到另外一块Survivor空间,最后清理掉Eden和Survivor空间
分代回收策略
不是一种具体的GC算法,是一种不一样代采起不一样的回收算法的策略.
新生代的对象生命周期短,只有少许存活,那就选用复制算法,只须要付出少许存活对象的复制成本就能够完成收集. 而老年代中由于对象存活率高,没有额外的空间进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收.
回收方法区(永久代)
此区域的回收主要有两项内容:废弃常量和无用的类
垃圾回收器一般是做为一个单独的低级别的线程运行, 不可预知的状况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,开发者不能实时的调用垃圾回收器对某个对象或全部对象进行垃圾回收. 常见垃圾回收器有:
应用标记-清除算法,具体过程有初始标记-并发标记-从新标记-并发清理-并发重置
并发收集、低停顿 ;但也有对CPU资源很是敏感、没法处理浮动垃圾,以及算法自己所具备的会产生内存碎片的缺点
Garbage-First(G1,垃圾优先)收集器是服务类型的收集器,目标是多处理器机器、大内存机器.
应用标记-整理算法,相对于CMS能很是精确地控制停顿,高度符合垃圾收集暂停时间的目标,同时实现高吞吐量.能够实如今基本不牺牲吞吐量的状况下完成低停顿的回收
内存划分方式: 它是将堆内存被划分为多个大小相等的 heap 区,每一个heap区都是逻辑上连续的一段内存(virtual memory). 并跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据容许的收集时间, 优先回收垃圾最多的区域(这也是Garbage First名称的由来). 总而言之,区域划分和有优先级的区域回收,保证了G1收集器在有限的时间内能够得到最高的收集效率.
对象主要分配在新生代的Eden区,少数状况下会直接分配在老年代中.
分配策略:
对象年龄断定
JVM给每一个对象都定义一个age计数器,若对象在Eden出生并通过第一次Minor GC后仍存在并被Survivor容纳,对象age为1,以后在Survivor中每通过一次Minor GC,age加1.当age达到必定数值(默认15)就会成为老年代,默认值能够经过-XX:MaxTenuringThreshold
修改.
实际上,JVM并非总要求对象的年龄必需达到MaxTenuringThreshold
才能晋升老年代,若是在Survivor空间中相同年龄全部对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就能够直接进入老年代.
参数 | 说明 |
---|---|
-XX:+UseSerialGC | 在新生代和老年代使用串行收集器 |
-XX:+UseParallelGC | 新生代使用并行回收收集器 |
-XX:+UseParallelOldGC | 老年代使用并行回收收集器 |
-XX:+UseParNewGC | 在新生代使用并行收集器 |
-XX:+UseConcMarkSweepGC | 新生代使用并行收集器,老年代使用CMS+串行收集器 |
-XX:+UseCMSCompactAtFullCollection | 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理 |
-XX:UseCtpancyOnly | 表示只在到达阀值的时候,才进行CMS回收 |
-XX:SurvivorRatio | 设置eden区大小和survivior区大小的比例 |
-XX:NewRatio | 新生代和老年代的比 |
-XX:ParallelGCThreads | 设置用于垃圾回收的线程数 |
-XX:ParallelCMSThreads | 设定CMS的线程数量 |
-XX:CMSInitiatingOccupancyFraction | 设置CMS收集器在老年代空间被使用多少后触发 |
-XX:CMSFullGCsBeforeCompaction | 设定进行多少次CMS垃圾回收后,进行一次内存压缩 |
-XX:+CMSClassUnloadingEnabled | 容许对类元数据进行回收 |
-XX:CMSInitiatingPermOccupancyFraction | 当永久区占用率达到这一百分比时,启动CMS回收 |
-XX:+PrintGCDetails | 开启后,GC时打印内存回收日志,并在线程退出时输出内存分配状况 |
Minor GC与Full GC分别在何时发生?何时触发Full GC;
类型 | GC对象 | 发生时机 |
---|---|---|
Minor GC | 回收年轻代, 包括Eden 和 Survivor 区域 | 没法为一个新的对象分配空间时 |
Major GC | 永久代 | |
Full GC | 整个堆空间 |
GC收集器有哪些?CMS收集器与G1收集器的特色。
Java中的大对象如何进行存储;
为何新生代内存须要有两个Survivor区?
G1停顿吗,CMS回收步骤,CMS为何会停顿,停顿时间;
每一个算法的优缺点啊, 怎么简单的解决啊
增长堆的大小, 增长后台线程, 提早开始并发周期等
有没有了解G1收集器这些, G1的流程, 相比CMS有哪些优点.
Minor GC发生的频繁的缘由?�GC的时间长的缘由是什么 对象过小, 对象太大
Full GC次数太多了,如何优化;
文章转载于:https://www.javazhiyin.com/24771.html