【Java中的JVM】(待整理)
1、JVM相关
1、JVM的功能:
a.通过ClassLoader寻找和装载class文件
b.解释字节码成为指令并运行,提供class文件运行环境
c.进行运行期间垃圾回收
d.提供与硬件交互的平台
2、垃圾回收:
1)虚拟器线程等待JVM到达安全点之后出现,操作必须在独立的线程里执行,因为当堆修改无法进行时,线程需要JVM位于安全点。VMThread包括stop-the-world垃圾回收、线程栈dump、线程暂停、线程偏向锁(basicObjectLock)解除。
2)safePoint安全点可以挂起线程,防止线程无限运行,一般位于循环末尾(防止大循环)、方法返回前、调用方法的call之后、抛出异常的位置。
3)safepoint 只能处理一些正在运行的线程,对于一些sleep()或block()的线程会被添加到safe region区域。标记safe region。当它被唤醒时,应该先检查GC是否完成操作。
4)GC的时候,所有进入safepoint的线程会在一个Thread.lock锁阻塞,直到当JVM的GC完成操作,JVM释放锁,阻塞的JAVA线程才能运行。
5)GC线程:这些线程支持JVM中不同的垃圾回收活动。
6)对象的回收:对象、数组存放在JVM堆中,分为新生代和老年代。新生代分为三个区,一个Eden、两个survivor,对象创建之后存在Eden(容量很大的对象可以创建到老年代)。新生代会执行MinorGC,98%的对象会被回收,不被回收的对象转移(复制算法)到一个survivor中,然后等待下一次MinorGC,GC之后Eden剩下的对象和survivor中的对象都被转移到另一个servivor中,对象就在两个survivor中不断转换。直到经历15次MinorGC才能进入老年代(old)。old中会执行FullGC,但比MinorGC的执行频率要低很多。FullGC一般耗时为MinorGC的22.89倍。新生代一般18M,老年代一般42M。
7)垃圾回收由新生代和年长代协作,称为分代回收,分别采用复制算法和标记整理算法。
复制算法:两个区域A和B,初始化对象在A,继续存活的对象被转移到另一个区。用在新生代的回收上。新生代分为一个Eden、两个survivor区。
标记整理算法:一块区域,对所有的对象进行标记(可达性标记),然后回收不可达对象,因为不是复制转移算法,所以会出现碎片。整理算法可以将碎片空间进行整理,整理出更大的内存空间存放更大的独享。
7)对象的回收机制:当前对象是否回收,主要是采用可达性分析,如果不可达,会进行一个F-Queue队列之中,在finalize方法执行过程中,会进行第二次标记是否可达,选择自救还是回收。垃圾回收线程在jvm中优先级相当的低。
8)程序开发者只能推荐JVM进行回收,但何时回收,回收哪些不能控制,-->可通过system.gc()来建议gc回收。垃圾回收只是回收不再被使用的JVM内存,与内存是否溢出没有直接关系。
9)真正宣布一个对象死亡:第一次标记-->调用finalize方法-->第二次gc回收。
10)各版本的垃圾回收器:
单线程收集器,在进行垃圾收集时,必须暂停其他所有的工作线程,直到它搜集结束。
多线程收集器,
jdk1.3 Serial New收集器:针对新生代,单线程收集器(使用复制收集算法)
jdk1.4 Parallel New收集器:并行回收,多线程收集器(新生代和年长代采用不同的算法)。
jdk1.4 Paraller Scavenge:并行,新生代多线程,吞吐量最大化,精确控制吞吐量。 吞吐量=运行用户代码/CPU运行时间(用户代码+垃圾回收)
jdk1.5 CMS(Concurrent Mark Sweep)目标:最短回收停顿时间。(标记-清除)
jdk1.5 Serial Old老年代版本,它同样是一个单线程收集器,(使用标记整理算法)
jdk1.6 Parallel Old并行,注重吞吐量以及CPU资源敏感的场合,可以优先考虑Parallel Scavenge+Parallel Old收集器组合。
jdk1.7 G1并行与并发、分代收集、空间整合、可预测的停顿,有意代替GMS。(整体标记整理,局部采用复制)
11)内存泄漏(Memory leak)是指一个不再被使用的对象或者变量还在内存中占用存储空间,在java语言中引入垃圾回收机制,有GC负责进行回收不再使用的对象,释放内存。但还是会出现内存泄漏,主要有两个情况:1)堆中申请的空间没有释放,2)对象仍保留连接引用(例如数据库连接)
12)内存泄漏的原因:如数据库连接、网络连接、IO连接,不再使用时如果连接不释放容易造成内存泄漏。释放对象时往往没有删除响应的监听器,可能造成内存泄漏。
13)内存溢出(OOM)是指程序在申请内存时没有足够的内存供使用,进而导致程序奔溃。内存泄漏最终导致内存溢出。
2、JVM维护了一个数据结构,记录了所有的线程,所以它可以快速检查所有线程的状态。
3、JVM通过控制主内存与每个线程的本地方法内存之间的交互,为java提供内存可见性(保证线程通信)。
4、如果使用jconsole或其他调试器,会看到很多线程在后台运行,主要有JVM线程、触发main方法的主线程以及主线程创建的其他线程一起运行。
5、JVM有两种执行方法:解释型和编译型(JIT)
在JIT执行方式下,将safepoint的检查代码加入到本地代码,当JVM需要线程进入safepoint时,只需要设置标志位,运行到标志位,如果标志位被设置则进入safepoint。
在解释型执行下,JVM会设置一个2字节的dispatch tables解释器,执行过程中会经常检查这个dispatch tables,当有请求发生时,则让线程进入safepoint。
6、周期性任务线程:该线程负责定时器事件(也就是中断),用来调度周期性操作的执行。
7、编译器线程:这些线程在运行时将字节码动态编译成本地平台相关的机器码。
8、信号分发线程:这个线程接收发送到JVM的信号并调用适应的JVM方法处理。
9. 虚拟机中的线程图示:
操作系统分配给每一个线程2G的内存,2G = 堆内存+方法区+程序计数器+本地栈+线程栈
一般线程栈有1000-2000栈帧就够用于递归,如果发生内存溢出==没有多余的内容分配给新对象,可以适当的减少栈的容量,来扩大堆的容量。
10. JVM中的堆栈划分:
配置信息: -Xmx10240m -Xms10240m -Xmn5120m -XXSurvivorRatio=3
参数含义:
-Xmx ==》最大堆大小 10240
-Xms ==》初始化堆大小 10240
-Xmn ==》年轻代大小 5120
新生代包括Eden 和 Survivor 一共三个区。
-XXSurvivorRatio ==》年轻代中Eden区和Survivor区的大小比值
Eden区 3072 两个Survivor区 2048
所以每个 Survivor 的大小为 1024 m