1、 JVM内存分区java
分为程序计数器、虚拟机栈、本地方法栈、Java堆、方法区5个区域程序员
其中Java堆和方法区是线程共享的,虚拟机栈、本地方法栈、程序计数器是线程隔离的。算法
程序计数器:数组
1.能够看做当前线程所执行的字节码的行号指示器缓存
2.Java多线程之间进行切换的时候须要以后恢复到以前执行位置,因此每条线程须要一个程序计数器,程序计数器是线程隔离的。安全
3.不会发生内存溢出数据结构
虚拟机栈(堆内存):多线程
描述的是Java方法执行的内存模型,每一个方法在执行的同时会建立一个栈帧用于存储局部变量表什么的。方法的调用和执行完成对应着 栈帧在虚拟机栈中的入栈和出栈。并发
局部变量表里放的是编译期可知的基本类型和对象引用和returnAdress。局部变量表所需的内存空间在编译期间完成分配,运行期间不会改变。jvm
线程请求的栈深度大于虚拟机容许会发生Stackoverflowerror,扩展栈时若是没法申请到足够空间会发生OutOfMemoryError.
本地方法栈:
与虚拟机栈相似,可是本地方法栈是为native方法服务的。
一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,好比C。
和虚拟机栈同样,Stackoverflowerror和OutOfMemoryError
Java堆:
被全部线程共享的区域,用来存放对象实例,几乎全部对象实例都在这分配内存
Java堆没有内存分配了而且没法扩展时抛出OutOfMemoryError
方法区:
全部线程共享的区域,用于存储已被加载的类信息、常量、静态变量等数据。
Java8以前HotSpot使用永久代实现方法区,如今Java8移除了永久代。改用了元空间,使用的是本地内存,非jvm区域。
没法知足内存分配时抛出OutOfMemoryError。
知道的GC算法有四种:标记-清除算法、复制算法、标记整理、分代收集算法。
标记-清除算法:
分“标记”和“清除”两个阶段:先标记出全部须要回收的对象,标记完成后统一回收。
最基础的收集算法,有两个缺陷:1.效率不高 2.会产生大量不连续的内存碎片,致使以后分配大对象空间不够,而不得又触发垃圾回收。
复制算法:
将可用内存按照容量划分为大小相等的两块,每次只使用其中一块。第一块用完了,就将第一块里存活的复制到第二块,再回收掉第一块所有。
这样不用考虑内存碎片且运行高效。可是代价是缩小一半可用,代价大。
而后实际上会把可用内存空间按8:1:1分为Eden和两块Survivor 并称为新生代,每次使用Eden和一块Survivor也就是90%,回收时把存活的对象放到剩余的那块Survivor上,再回收。减小浪费。若是剩余那块Survivor放不下,就会放到Java堆的老年代里去。
标记-整理算法:
因为可能存在大量对象存活的状况,那么复制算法复制较多效率会变低。
而根据老年代存活对象较多的特色,用标记-整理算法,与标记清除算法区别在于标记后得把活着的整理到一端,再把另外一端回收。
分代收集算法:
并无新的收集算法,而是把Java堆按2:1分为新生代和老年代,根据特色选用收集算法。新生代采用复制算法,老年代采用标记-整理或标记-清除。
Serial收集器:
单线程收集器,只会使用一条线程去完成收集,在收集时必须暂停其它全部工做线程。
缺点很明显,但优势在于简单而高效,没有线程交互开销
在clien的用户程序模式下,暂停几十毫秒,用户感觉不到,能够接收。
ParNew收集器:
Serial的多线程版本,多线程去垃圾回收.
Server模式下虚拟机首选的新生代收集器,缘由在于除Serial收集器外只要它能够和CMS这款强大的并发老年代收集器配合。
ParallelScavenge收集器:
特色在于关注点和其它收集器不一样,CMS等收集器关注点在于尽量缩短用户线程的停顿时间,而Parallel Scavenge收集器目的是控制吞吐量,即控制用户代码时间在用户代码时间+GC时间中的比例。
SerialOld收集器:
Serial收集器的老年代版本,采用标记-整理算法。主要给客户端模式下的虚拟机使用。在Server模式下两个用途:jdk1.5以前和Parallel Scavenge收集器搭配使用,其次是做为CMS收集器的后备预案
ParallelOld收集器:
ParallelScavenge收集器的老年版本。与新生代收集器Parallel Scavenge 配合实现吞吐量的控制和利用多处理器能力。
CMS收集器:
以最短回收停顿时间为目标的收集器,“标记-清除”算法,过程分为四个步骤:
1. 初始标记:标记GC Roots能直接关联到的对象
2. 并发标记:顺着GC Roots寻找的过程
3. 从新标记:修改并发标记期间程序运行而改动的对象
4. 并发清除
其中初始标记、从新标记须要中止其它工做线程,并发标记和并发清除不须要。
因为整个过程当中耗时最长的并发标记和并发清除过程是能够和用户线程一块儿的,因此整体上CMS收集器是能够和用户线程一块儿的。
优势:并发收集、低停顿
缺点:
1. 占用大量CPU资源
2. CMS收集器没法处理浮动垃圾,可能会出现“Concurrent Mode Failure(并发模式故障)”失败而致使Full GC产生。
浮动垃圾:因为CMS并发清理阶段用户线程还在运行着,伴随着程序运行天然就会有新的垃圾不断产生,这部分垃圾出如今标记过程以后,CMS没法在当次收集中处理掉它们,只好留待下一次GC中再清理。这些垃圾就是“浮动垃圾”。
3. 标记-清除容易产生空间碎片,容易提早触发Full GC。
G1收集器:
但愿将来能够替代CMS的收集器。将Java堆划分为多个大小相等的独立区域。
1. 并行和并发
2. 分代收集,且G1就能够独立管理整个Java堆
3. 总体上采用“标记-整理”、局部(两个独立区域之间)上是复制算法。
4. 能够指定某个时间片断上,垃圾收集不超过的时间:维护了独立区域的回收价值优先列表,优先回收回收价值高的(Garbage First名字由来),实现指定时间效率。
回收步骤:
1. 初始标记(标记GC Roots能直接关联到的对象)
2. 并发标记(顺着GC Roots寻找的过程,只有这个是并发的,表示和用户一块儿)
3. 最终标记(修改并发标记期间程序运行而改动的对象)
4. 筛选回收
Full GC定义是相对明确的,就是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC;Minor GC和Major GC是俗称,对应着Young GC和Old GC
Minor GC ,FullGC 触发条件
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,可是没必要然执行
(2)老年代空间不足
1.包括复制算法移到这时,
2.堆上分配大对象或大数组直接进入老年代时,
3.经过Minor GC时,计算以前晋升到老年代的平均大小,当老年代剩余空间小于平均值时
(3)若是有永久代的话,永久代不够用也会触发
(4)CMS GC时出现concurrent mode failure
强引用、软引用、弱引用、虚引用。
强引用:是指建立一个对象并把这个对象赋给一个引用变量。
好比:Object object =new Object();,只有强引用还在,就不会回收
软引用:若是一个对象具备软引用,内存空间足够,垃圾回收器就不会回收它;
若是内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就能够被程序使用。可使用SoftReference类实现。
弱引用:弱引用也是用来描述非必需对象的,比软引用更弱,当JVM进行垃圾回收时,不管内存是否充足,都会回收被弱引用关联的对象。能够用WeakReference类实现
虚引用:不影响对象的生命周期。java中用PhantomReference类表示。若是一个对象与虚引用关联,则跟没有引用与之关联同样,在任什么时候候均可能被垃圾回收器回收。惟一目的是为了回收时有个系统通知。
主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。基本思路就是经过一系列名为"GC Roots"的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的,下图对象object5, object6, object7虽然有互相关联,但它们到GCRoots是不可达的,因此它们将会断定为是可回收对象。
虚拟机栈中引用的对象、
方法区类静态属性引用的对象、
方法区常量引用的对象、
本地方法栈JNI(Native方法)引用的对象
1.内存泄漏:是指程序在申请内存后,没法释放已申请的内存空间,一次内存泄漏彷佛不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
2、内存溢出指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,可是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
3、两者的关系
内存泄漏的堆积最终会致使内存溢出内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统至关于无法知足你的需求,就会报内存溢出的错误。内存泄漏是指你向系统申请分配内存进行使用(new),但是使用完了之后却不归还,结果你申请到的那块内存你本身也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给须要的程序。
商业虚拟机都采用分代收集算法,将Java堆按1:2的比例分红新生代和老年代。根据不一样年代采用不一样收集算法,新生代存的都是新生的对象,垃圾收集时大多都会死,适合采用复制算法。而老年代存储的都是生命周期较长的,回收的较少,适合采用标记-整理或标记-清除。
分代的目的:(缩短GC致使的停顿时间)
一般GC的整个工做过程当中都要“stop-the-world”,若是能想办法缩短GC一次工做的时间长度就是件重要的事情。若是说收集整个GC堆耗时太长,那不如只收集其中的一部分
新生代和老年代存储的是什么:这就关于jvm内存分配的问题:
新生代:新建立的对象优先在新生代的Eden上分配。
老年代:
1.新生代对象每经历依次minor gc,年龄会加一,当达到年龄阀值会直接进入老年代。阀值大小通常为15
2.Survivor中年龄相同的对象数量的总和大于survivor空间的一半,年龄大于或等于该年龄的对象就能够直接进入老年代,而无需等到年龄阀值
3.大对象大数组直接进入老年代
4.新生代复制算法须要一个survivor区进行轮换备份,若是出现大量对象在minor gc后仍然存活的状况时,就须要老年代进行分配担保,让survivor没法容纳的对象直接进入老年代
什么是类加载机制:
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终造成能够被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。以下:
有加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析统称为链接。其中除解析外,其它都是按顺序开始的(几个步骤可能同时进行),解析则有可能在初始化以后才开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。
虚拟机对于类的初始化阶段严格规定了有且仅有只有5种状况若是对类没有进行过初始化,则必须对类进行“初始化”!
1. 遇到new、读取一个类的静态字段(getstatic)、设置一个类的静态字段(putstatic)、调用一个类的静态方法(invokestatic)。
2. 使用java.lang.reflect包的方法对类进行反射调用时。
3. 当类初始化一个类的时候,若是发现其父类尚未进行过初始化,则须要先触发其父类的初始化。(若是是接口,则没必要触发其父类初始化)
4. 当虚拟机执行一个main方法时,会首先初始化main所在的这个主类。
5. 当只用jdk1.7的动态语言支持时,若是一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且这个方法句柄所对应的类没有进行过初始化,则须要先触发其初始化。(暂未研究此种场景)
验证是链接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区进行分配。而实例变量是在类实例化的时候初始化,和类加载不要紧!
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
类初始化阶段是类加载过程的最后一步,前面的类加载过程当中,除了在加载阶段用户应用程序能够经过自定义类加载器参与以外,其他动做彻底由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的 Java 程序代码(或者说是字节码)。
对于任意一个类,都须要由加载它的类加载器和这个类自己一同确立在Java虚拟机的惟一性。比较两个类是否相等不能光看他们自己来自同一个类,同时也要看加载它们的类加载器是否相同。
绝大部分Java程序会使用到如下3种系统提供的类加载器。
1. 启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++实现,是虚拟机自身的一部分。负责加载<JAVA_HOME>\lib中(或者-Xbootclasspath参数所指定路径),而且是虚拟机识别的类库 加载到虚拟机内存中。不可直接被Java程序引用。
2. 扩展类加载器(Extension ClassLoader),这个类加载器由Java实现,独立于虚拟机外部。负责加载<JAVA_HOME>\lib\ext中(或者被java.ext.dirs系统变量所指定)的全部类库。开发者可直接使用扩展类加载器。
3. 应用程序类加载器(Application ClassLoader),也称之为系统类加载器,一样也由Java实现,独立于虚拟机外部。负责加载用户类路径(ClassPath)上所指定的类库。开发者可直接使用这个类加载器。
4. 若是有必要能够本身定义类加载器(继承ClassLoader,重写方法)
若是一个类加载器收到了类加载的请求,它首先不会本身去尝试这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,所以全部的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈本身没法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试本身去加载。
对Java程序稳定运做很重要,防止类混乱,好比你就不可能去编写java.lang.Object了,层层上交到顶层会发现已存在。
能不能本身写个类叫java.lang.System?
答案:一般不能够,但能够采起另类方法达到这个需求。
解释:为了避免让咱们写System类,类加载采用委托机制,这样能够保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而System类是Bootstrap加载器加载的,就算本身重写,也老是使用Java系统提供的System,本身写的System类根本没有机会获得加载。
可是,咱们能够本身定义一个类加载器来达到这个目的,为了不双亲委托机制,这个类加载器也必须是特殊的。因为系统自带的三个类加载器都加载特定目录下的类,若是咱们本身的类放在一个特殊的目录,那么系统的加载器就没法加载,也就是最终仍是由咱们本身的加载器加载。
java是面向对象的语言,所以对象的建立无时无刻都存在。在语言层面,使用new关键字便可建立出一个对象。可是在虚拟机中,对象建立的建立过程则是比较复杂的。
首先,虚拟机运到new指令时,会去常量池检查是否存在new指令中包含的参数,好比newPeople(),则虚拟机首先会去常量池中检查是否有People这个类的符号引用,而且检查这个类是否已经被加载了,若是没有则会执行类加载过程。
在类加载检查事后,接下来为对象分配内存固然是在java堆中分配,而且对象所须要分配的多大内存在类加载过程当中就已经肯定了。为对象分配内存的方式根据java堆是否规整分为两个方法:1、指针碰撞(Bump thePointer),2、空闲列表(Free List)。指针碰撞:若是java堆是规整的,即全部用过的内存放在一边,没有用过的内存放在另一边,而且有一个指针指向分界点,在须要为新生对象分配内存的时候,只须要移动指针画出一块内存分配和新生对象便可;空闲列表:当java堆不是规整的,意思就是使用的内存和空闲内存交错在一块儿,这时候须要一张列表来记录哪些内存可以使用,在须要为新生对象分配内存的时候,在这个列表中寻找一块大小合适的内存分配给它便可。而java堆是否规整和垃圾收集器是否带有压缩整理功能有关。
在为新生对象分配内存的时候,同时还须要考虑线程安全问题。由于在并发的状况下内存分配并非线程安全的。有两种方案解决这个线程安全问题,1、为分配内存空间的动做进行同步处理;2、为每一个线程预先分配一小块内存,称为本地线程分配缓存(ThreadLocal Allocation Buffer, TLAB),哪一个线程须要分配内存,就在哪一个线程的TLAB上分配。
内存分配后,虚拟机须要将每一个对象分配到的内存初始化为0值(不包括对象头),这也就是为何实例字段能够不用初始化,直接为0的缘由。
接来下,虚拟机对对象进行必要的设置,例如这个对象属于哪一个类的实例,如何找到类的元数据信息。对象的哈希吗、对象的GC年代等信息,这些信息都存放在对象头之中。
执行完上面工做以后,全部的字段都为0,接着执行<init>指令,把对象按照程序员的指令进行初始化,这样一个对象就完整的建立出来。
即便在可达性分析算法中不可达的对象,也并不是“非死不可”的,这时候它们处于“缓刑”阶段。一个对象的真正死亡,至少要经历两个标记过程:若是对象在进行可达性分析后发现没有与GC Roots相链接的引用链,那它将会被第一次标记而且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种状况视为“没有必要执行”。
若是这个对象被断定为有必要执行finalize()方法,那么这个对象会被防止在一个叫作F-Queue的队列中,并在稍后由一个虚拟机自动创建的、低优先级的Finalizer线程去执行它,但并不承诺会等待它运行结束,这样作的缘由是,若是一个对象在finalize方法中执行的很慢,或者产生了死循环,将极可能到值F-Queue队列中的其余对象永久处于等待,甚至致使真个内存回收系统崩溃。
Finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,若是对象要在finalize()中成功拯救本身-----只须要从新与因用力按上的任何一个对象创建关联即,好比把本身赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集合;若是对象这时候尚未逃脱那基本上就真的被回收了。
各主要JVM启动参数的做用以下:
-Xms:(堆内存初始值)Java Heap初始值,Server端JVM最好将-Xms和-Xmx设为相同值,开发测试机JVM能够保留默认值;
-Xmx:(堆内存最大值)Java Heap最大值,默认值为物理内存的1/4,最佳设值应该视物理内存大小及计算机内其余内存开销而定;
-Xmn:(堆年轻代大小)Java HeapYoung区大小(统一最大最小都是它),不熟悉最好保留默认值;
-Xss:设置每一个线程的堆栈大小(也就是说,在相同物理内存下,减少这个值能生成更多的线程)
-XX:NewRatio:设置年轻代与老年代之比,如-XX:NewRatio=4就表示年轻代与老年代之比为1:4
-XX:SurvivorRatio=8:设置新域中Eden区与Survivor区的比值。则比例为1:8,总共1:1:8
十5、jvm调优工具与基本思路
工具备:jdk的bin目录下命令行工具JStack,Jmap等,可视化工具JConsole、visualVM
查看堆空间大小分配(年轻代、年老代、持久代分配)
垃圾回收监控(长时间监控回收状况)
线程信息监控:系统线程数量
线程状态监控:各个线程都处在什么样的状态下
线程详细信息:查看线程内部运行状况,死锁检查
CPU热点:检查系统哪些方法占用了大量CPU时间
内存热点:检查哪些对象在系统中数量最大
大体能够分为3个过程:
1.解析与填充符号表过程。
--词法、语法分析
--填充符号表
2.插入式注解处理器的注解处理过程。
3.语义分析与字节码生成过程。
根据GC日志状况,新生代、老年代使用状况而定….