首页 > 技术文章 > JVM学习笔记

fyboke 2017-03-30 15:31 原文

1.自己编译一个JDK
大致步骤:
首先下载JDK源码(例如OpenJdk7,OpenJdk6等)
如果是在windows平台下需要安装CYGWIN模拟linux环境
安装编译器,JDK最核心的代码是使用C++和少量的C编写的,所以要安装(Microsoft Visual Studio C++ 2010)
在这还要安装一个编译好的JDK,为什么呢,因为JDK包含的各个部分有的是使用C++编写的,而更多是使用java自身实现的,所以需要给自身提供一个环境
最后需要安装一个Apache ANT,因为JDK中的部分代码是使用ANT脚本进行编译的。

当然这只是大致的步骤,我们知道JDK的核心代码还是没有公开的,所以我们还需要这部分资源,官方称之为“JDK Plug”,在windows平台下是以JAR包形式存在的

2.JVM中的内存分配
程序计数器:它是一块较小的内存空间,主要作用可以看做是当前线程所执行的字节码的行号指示器,既然说到当前线程,那么它就是线程私有的,不共享;
如果线程执行的是java方法,则记录字节码指令地址;如果执行的是Native方法,则计数器的值为空;
所以此内存区域是唯一一个在java虚拟机中没有规定任何OutOfMemoryError情况的区域。

java虚拟机栈:每个方法被执行的时候都会创建一个栈帧用于存储变量表,操作栈,动态连接和方法出口等信息,他的生命周期和线程相同,所以它也是线程私有的,不共享
一般会发生两种类型的异常:如果线程请求的栈的深度大于虚拟机所允许的深度,将抛出stackoverflowerror异常;
如果虚拟机栈可以动态的扩展,但无法申请到足够的内存时会抛出outofmemoryerror;

本地方法栈:和java虚拟机栈差不多,不同的是虚拟机栈为虚拟机执行java方法服务,本地方法栈是为本地方法服务,也会抛出stackoverflowererror和outofmemoryerror异常

java堆:保存对象的地方,数组对象也放在此处,可以被所有线程共享;
他也是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”,由于现在的收集器基本都采用分代收集算法,所以java堆中还可以细分为新生代,老年代等
当前主流的虚拟机都是可扩展的(通过-Xmx和Xms控制)。如果在堆中没有内存完成实力分配,且堆无法在扩展时,将会抛出outofmemoryerroe异常。

方法区:用于存储已被java虚拟机加载的类信息,常量,静态变量,也是各个线程共享的内存区域;
根据java虚拟机规范的规定,当方法区无法满足内存分配的需求时,将抛出内存溢出异常

运行时常量池:它是方法区域的一部分,用于存放编译器生成的各种字面量和符号引用,由于收到内存的限制,无法在申请到内存时会抛出内存溢出异常

直接内存:直接内存并不是虚拟机运行时数据区域的一部分,但是这部分内存被频繁的使用,也会导致内存溢出异常

3.GC最基础的收集算法
标记-清除算法:标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
缺点:效率不高;
空间问题:标记清除之后会产生大量不连续的内存碎片,会导致程序再分配大对象的时候无法找到足够的连续内存空间
而不得不提前触发另一次垃圾回收动作。
复制算法:它可将可用内存按容量划分为等大的两块,每次只使用其中的一块,当这块内存用完的时候,就将还存活着的对象复制到另外一块上
然后再把已使用过的内存全部删除,这种算法的缺点就是会造成内存的浪费

标记-整理算法:标记的过程和标记-清除算法一样,但是后续的步骤不是直接清除可回收对象,而是将存活的对象整理一下都向一段移动,然后直接清理掉端边以外的内存

分代收集算法:这是当前商业虚拟机普遍采用的算法,但是这种算法没有什么新的思想,只是根据对象的存活周期的不同将内存划分为几块,一般是分为新生代,老年代,这样
根据各个年代的特点采用最适合的收集算法,比如新生代中,每次都会发现大量的对象死去,采用复制算法,只需要付出少量存活对象的复制成本就可以,二老年代中对象的存活
率比较高,没有额外的空间对他进行分配,就会采用标记-清理或者标记-整理算法。

推荐阅读