代码改变世界

JVM

2020-03-20 23:43  DataBases  阅读(198)  评论(0编辑  收藏  举报

JVM是可运行Java代码的假想计算机,包括一套字节码指令集,一组寄存器,一个栈,一个垃圾回收,堆和一个存储方法域。JVM运行在操作系统之上,它与硬件没有直接的交互。

1.8之前

 

 1.8

 

 

JVM
1.java代码的执行
代码编译为class文件,javac
装载class文件,classLoader
执行class
解释执行
编译执行 client compiler ,server compiler
2.内存管理
内存空间:
方法区,堆,方法栈,本地方法栈,pc寄存器
内存分配:
堆上分配,TLAB分配,栈上分配
内存回收
算法 Copy,Mark-Sweep,Mark-Compact
Sun JDK
分代回收
新生代可用的GC:串行copying,并行回收copying,并行copying
Minor GC触发机制以及日志格式
旧生代可用的GC:串行Mark-Sweep-Compact,并行Compacting,并发Mark-=Sweep
Full GC触发机制以及日志格式
内存状况分析:jconsole,visualvm,jstat,jmap,MAT
3.线程资源同步和交互机制
线程资源同步
线程资源执行机制
线程资源同步机制
synchronized的实现机制
lock/unlock的实现机制
线程交互机制
Object.wait/notify,notifyAll,Double check patten
并发包提供的交互机制:semaphore,countdownLath
线程状态及分析方法:jstack,TDA
运行过程:
1.java 源文件->编译器->字节码文件
2.字节码文件->JVM->机器码
每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是 Java 为什么能够
跨平台的原因了 ,当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序启动就会
存在多个虚拟机实例。程序退出或者关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不
能共享。
运行:
JVM允许一个应用并发执行多个线程,Hotspot JVM中的Java线程与原生操作系统线程有直接的映射关系。
当线程本地存储,缓冲区分配,同步对象,栈,程序计数器等准备好以后,就会创建一个操作系统原生线程。
Java线程接收,原生线程随之被收回。操作系统负责调度所有线程,并把他们分配到任何可用的CPU上。当原生线程初始化完毕,就会调用
java线程的run()方法。当线程结束时,会释放原生线程和Java线程的所有资源。

Hotspot VJVM后台运行的系统线程主要有下面几个;
虚拟机线程(VM thread):这个线程等待JVM到达安全点操作出现。这些操作必须要在独立的线程里执行,因为当堆修改无法进行是,线程都
需要JVM位于安全点。这些操作的类型有:stop-the-word 垃圾回收,线程栈dump,线程暂停,线程偏向锁(biased locking)解除。

周期性任务线程:这线程负责定时器事件(中断),用来调度周期性操作的执行。
GC线程:这些线程支持JVM中不同的垃圾回收活动。
编译器线程:这些线程在运行是将字节码动态编译成本地平台相关的机器码。
信号分发线程:这个线程接收发送到JVM的信号并调用适当的JVM方法处理。
JVM内存区域主要分为
线程私有区域:
程序计数器
虚拟机栈
本地方法区
线程私有区域生命周期与线程相同,依赖用户线程的启动/结束 而创建/销毁(在Hotspot VM内)。
线程共享区域:
JAVA堆,方法区
直接内存
不是JVM运行时数据区的一部分,可以使用Native 函数库直接分配堆外内存,然后使用DirectByebuffer对象
作为这块儿内存的引用进行操作;这样就避免了在JAVA堆和Native堆中来回复制数据,因此,在一些场景中可以显著提高性能。

线程共享区域随虚拟机的启动/关闭而创建/销毁。

JVM运行时内存
Java堆从GC的角度还可以细分为: 新生代(Eden区、From Survivor区和To Survivor区)和老年代。
新生代
是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。新生代又分为 Eden区、ServivorFrom、ServivorTo三个区。
Eden区
Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
ServivorFrom
上一次GC的幸存者,作为这一次GC的被扫描者。
ServivorTo
保留了一次MinorGC过程中的幸存者。

MinorGC的过程(复制->清空->互换),MinorGC采用复制算法。

老年代
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。
MajorGC采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。

永久代
指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。
在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制。

如何确定垃圾
引用计数法
在Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用,即他们的引用计数都为0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
可达性分析
为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
标记清除算法(Mark-Sweep) 最基础的垃圾回收算法,分为两个阶段,标注和清除。
标记阶段标记出所有需要回收的对象;
清除阶段回收被标记的对象所占用的空间。
该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。


复制算法(copying) 为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。
这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying算法的效率会大大降低。
标记整理算法(Mark-Compact)
结合了以上两个算法,为了避免缺陷而提出。标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
分代收集算法 分代收集法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
新生代与复制算法 目前大部分JVM的GC对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1来划分新生代。一般将新生代划分为一块较大的Eden空间和两个较小的Survivor空间(From Space, To Space),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor空间中。
老年代与标记复制算法 而老年代因为每次只回收少量对象,因而采用Mark-Compact算法。 1. JAVA虚拟机提到过的处于方法区的永生代(Permanet Generation),它用来存储class类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。 2. 对象的内存分配主要在新生代的Eden Space和Survivor Space的From Space(Survivor目前存放对象的那一块),少数情况会直接分配到老生代。 3. 当新生代的Eden Space和From Space空间不足时就会发生一次GC,进行GC后,Eden Space和From Space区的存活对象会被挪到To Space,然后将Eden Space和From Space进行清理。 4. 如果To Space无法足够存储某个对象,则将这个对象存储到老生代。 5. 在进行GC后,使用的便是Eden Space和To Space了,如此反复循环。 6. 当对象在Survivor区躲过一次GC后,其年龄就会+1。默认情况下年龄到达15的对象会被移到老生代中。