不论什么语言符合jvm字节码的规范,就可以在jvm上运行
深入java虚拟机的作者说过:不知道 java虚拟机在语言上无关性的优势会不会赶超它在平台无关性的优势?
jvm<jre<jdk
jvm运行流程:
java源程序 -> 编译为字节码文件 -> 传送到jvm虚拟机 -> jvm不同操作系统转换为不同的机器指令
(这就是为什么windows和linux下有不同版本的jdk,在不同系统平台下安装有不同的jvm;这也是jvm跨平台的原因)
===========================================================================================
java内存区域与内存溢出回收
运行时数据区:
堆、栈、方法区、本地方法栈、程序计数器,jdk1.8有变动
堆:线程共享,存储:对象或数组;堆中划分为:年轻代,老年代
(HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)默认比例为8:1
=> 相当于Eden为80%,1个Survivor占比10%
=> 因为年轻代中的对象基本都是朝生夕死的(80%以上))
===========================================================================================
以下这段摘自:http://ifeve.com/jvm-yong-generation/
一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到老年代中。
因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
一个对象的这一辈子
我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
以上这段摘自:http://ifeve.com/jvm-yong-generation/
===========================================================================================
栈/本地方法栈:线程私有,存储:局部变量(各种基本数据类型)、对象引用,
(本地方法栈为虚拟机使用到的native方法服务)
i:方法在执行时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等
ii:方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
iii: 局部变量表所需的内存空间在编译期间完成分配,而且分配多大的局部变量空间是完全确定的,
在方法运行期间不会改变其大小
iv:出栈后空间释放
方法区:线程共享,存储:类信息、常量、静态变量、方法字节码
运行时常量池是方法区的一部分(用于存放编译器生成的各种字面量和符号引用)
Java8之前,常量池是存放在堆中的,常量池就相当于是在永久代中,所以永久代存放在堆中。
Java8之后,取消了整个永久代区域,取而代之的是元空间。常量池就不存放在堆中,而是存放在方法区里面,
与堆栈是并列关系。永久代也就不存放在堆中了。(方法区里面的运行时常量池)
方法区只是一个规范,1.8之后放在元空间中 :https://www.cnblogs.com/dennyzhangdd/p/6770188.html
程序计数器:线程私有,
当前线程执行的字节码的行号指示器,通过改变此指示器来选取下一个需要执行的字节码指令
特征:在线程创建时创建、每个线程拥有一个、指向下一条指令的地址
===========================================================================================
以下这段摘自:http://www.cnblogs.com/gw811/archive/2012/10/19/2730258.html
其中程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此这几个区域的内存分配和回收都具备确定性,在这几个区域内不需要过多考虑回收的问题,因为方法结束或线程结束时,内存自然就跟随着回收了。而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存,本书后续讨论中的“内存”分配与回收也仅指这一部分内存。
以上这段摘自:http://www.cnblogs.com/gw811/archive/2012/10/19/2730258.html
意思是垃圾回收主要指的是:堆 和 方法区
小李子:
局部对象的引用是放在栈中,全局的静态对象引用是放在方法区中,栈随方法的结束而回收局部引用(对象没了引用自然就会被回收了);而方法区中的常量引用不会被回收,该对象就一直存在!
static、与 final修饰的变量存放?
被static修饰的话,在类加载的时候,就会被创建。同类一样,存储在方法区中。
被final修饰的话, 方法区中是有一个常量池,专门放置常量。
(局部的基础类型的引用和值都放栈中,final 修饰的话不改变内存中的生命周期,而只是值不可被修改)
注意:成员变量放在堆中对应的对象里面,随该对象的回收而回收!(JDK1.8引进了元空间,静态变量和常量放在元空间)
===========================================================================================
直接内存:
直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。但是这部分内存也被频繁使用,而且也可能导致OutOfMemoryError异常出现,在jdk1.4中新加入了NIO,一种基于通道(Channel)与缓冲区(Bufffer)的I/O方式,它可以使用Native函数库直接分配对外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆和Native堆中来回复制数据。本机直接内存的分配不会受到java堆大小的限制,但是,既然是内存,还是会受到本机总内存的限制,有些时候在配置虚拟机参数的时候,忽略了直接内存,使得各个区域总和大于物理内存限制,从而出现OutOfMemoryError。
===========================================================================================
垃圾回收:
为什么需要GC与内存分配?
当需要排查各种内存溢出、内存泄漏,当垃圾收集器成为系统达到更高并发量的瓶颈的时候,我们就需要对这些“自动化”的技术实施必要的监控和调节。
对象已死么?
判断对象是否还存活的方法:
1.引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它的时候,计数器就加1;当失效的时候就减1
缺点:可能存在存在对象相互循环引用的问题,例如:objectA.instance = objectB , objectB.instance = objectA
2.可达性分析算法
通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则可以证明对象不可用。
可以作为GC Roots的对象包括:虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(即一般说的Native)引用的对象
再谈引用:
1.强引用 (Object obj = new Object())
2.软引用
3.弱引用
4.虚引用(幽灵引用或者幻影引用,未存在的唯一目的就是能在这个对象被收集器回收的时候收到一个系统通知)
生成还是死亡
如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,如果这个对象被判定有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。finalize()方法是对象逃过命运的最后一次机会,只要重新与引用链上的任何一个对象建立关联即可,例如把自己(this关键字)赋值给某个类变量或者对象的成员变量。
回收方法区
很多人认为方法区或者(HotSpot虚拟机中的永久代)是没有垃圾收集器的,因为它的性价比比较低,永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
如何判断是无用的类:
1.该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例
2.加载该类的ClassLoader已经被回收
3.该类对应的 java.lang.Class对象没有在任何地方被引用,无法在任何地方反射该类的方法
垃圾收集算法?
1.标记-清除算法
不足:1.标记与清除两个过程的效率都不高 2.会产生大量不连续的内存碎片(碎片太多,会导致分配较大的对象的时候,无法找到足够的连续内存)
2.复制算法
将可用内存按容量划分为大小相等的两块,每次只用其中一块,当这一块的内存用完了,就将还存活的对象复制到另外一块上,然后把已经使用的内存空间一次清理掉。
优点:实现简单、运行高效
缺点:直接将内存缩小为一般(代价太高)
3.标记-整理算法
与标记-清除的区别是,后续不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界之外的内存(适合老年代的垃圾收集)
4.分代收集算法
根据各个年代的特点采用最适合它的收集算法,例如:新生代采用复制算法,老年代采用“标记-清除”或者“标记整理”
垃圾收集器?
Serial收集器
优点:简单高效(与其它收集器的单线程比,限定于单个CPU),它依然是虚拟机运行在Client模式下的默认新生代收集器
缺点:单线程收集器,需要把用户正常工作的线程全部停掉
ParNew收集器:
其实就是Serial收集器的多线程版本,它是许多运行在Server模式下的虚拟机中首选的新生代收集器
并行:多条垃圾收集线程并行工作,但用户线程任然处于等待状态
并发:指用户线程与垃圾收集线程同时执行
Parallel Scavenge收集器
它是一个新生代收集器,特点:它的目的是为了达到一个可控制的吞吐量,吞吐量就是CPU用于运行用户代码的时间与CPU总消耗的时间的比值。
Serial Old 收集器
它是Serial收集器的老年代版本,使用“标记-整理”算法,也是给Client模式下的虚拟机使用,或者作为CMS收集器的后备预案
Parallel old收集器
它是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
CMS收集器(重点)
实现分为:初始标记->并发标记->重新标记->并发清除
优点:
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,B/S系统希望系统停顿时间最短,以给用户带来较好的体验,CMS就非常符合这类需求。
缺点:
1.对CPU资源非常敏感
2.产生浮动垃圾(CMS并发清除阶段,用户线程还在运行着,有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理,只好留在下一次GC的时候,这一部分垃圾就称为浮动垃圾)
3.CMS是基于“标记-清除”算法实现的,会产生大量的碎片
G1收集器
它是一款面向服务端应用的垃圾收集器
特点:并行与并发、分代收集、空间整合、可预测的停顿
使用G1收集器时,java堆的内存布局就与其他收集器有很大的差别,它将整个java堆划分为多个大小相等的独立区域,虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分(不需要连续)的集合
大对象:
所谓的大对象是指:需要连续内存空间的对象,最典型的大对象就是那种很长的字符串以及数组(例如:byte[],更坏的消息是短命大对象,写程序的时候应该避免)。
===========================================================================================