JVM基础知识

2. Java运行时环境 — 其实就是考JVM

JVM与Hotspot的关系:JVM是规范,Hotspot是对规范的实现。

JVM三个部分必须非常清楚:内存划分、class加载机制、GC策略。

1)内存划分

我们常说的对于内存的划分,通常是指Hotspot的划分方式,而非JVM规范所规定的。
Hotspot将JVM内存划分为三个部分,Young Generation(年轻代)、Old Generation(年老代)、Perm Generation(永久代)。其中,Young Generation又分为Eden、From和To. 其中From和To又统称为Survivor Spaces(幸存区)。

对于内存的划分,可以没事用内存分析工具看看,如jmap, jvisualvm等等,观察下各个区域实际的变化。

JVM内存区模型:





















<1> 方法区:为多个线程共享
即“永久代”。存储:常量、静态变量、虚拟机加载的类信息
运行时常量池:是方法区的一部分。

<2> 虚拟机栈:线程私有
存放局部变量表、操作栈、方法出口等信息。每个方法从被调用到执行完的过程,都对应着一个栈帧在虚拟机栈中的从入栈到出栈的过程。

局部变量表存放的内容:
1)编译器已知的基本数据类型:boolean, byte, char, short, int, float, long, double.
2)对象引用(非对象本身)
注:double和long类型的数据会占用2个局部变量的空间,而其余数据类型只占1个。

<3> 本地方法栈:与虚拟机栈的区别是虚拟机栈为虚拟机执行的方法服务,而本地方法栈为Native方法服务。

<4> 堆:为多个线程共享
堆分为新生代和老年代

<5> 程序计数器:当前线程所执行字节码的行号指示器。



怎样判断是局部变量表呢?局部变量表所需的空间在编译期间就完全确定,在运行期间不会改变。

直接内存:除了JVM内存,Java用到的还有直接内存,即不是JVM规范中定义的内存区域。在NIO中引入了通道与缓冲区的IO方式,可以调用native方法直接分配堆外的内存,这个堆外的内存就是本机内存,本机内存不会影响堆内存的大小。

2)classloader机制

关于classloader,可以结合tomcat,了解清楚tomcat和classloader机制,看看tomcat是如何保证各个APP之间的类的隔离的。
待续。。。

JVM把class文件加载到内存,并对数据进行校验、解析、初始化,最终形成JVM可以直接使用的Java类型的过程,即为class loader机制。

类从被加载到JVM内存中开始,到卸载出内存为止,它的生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段,其中验证、准备、解析三个部分成为链接。












解析和初始化的顺序有时会改变,有时在初始化之后再开始解析,如运行时动态绑定。

<1> 加载

主要步骤为:
通过“类的全名”来获取定义这个类的二进制字节流。
将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

加载阶段可以使用系统提供的类加载器(ClassLoader)来完成,也可以自己实现类加载器。

<2> 验证:验证的主要目的是确保class文件的字节流是否符合当前虚拟机的要求,且不会危害虚拟机自身安全。

验证阶段分四个过程:文件格式验证、元数据验证、字节码验证、符号引用验证。

文件格式校验:class文件是否以魔术数0xCAFEBABE开头(class文件以cafebabe开头),主、次版本号是否在当前虚拟机处理范围内

元数据校验:对字节码做语义分析,看是否符合java语言规范。如除Object类以外的类是否存在父类,是否final类有被其他类继承等等。

字节码校验:对数据流和控制流分析,分析类的方法在运行时会不会有危害虚拟机的行为,如不能把一个父类对象赋值给子类对象。

符号引用校验:检查属性和方法的访问性(private, protected, public, default)是否可被当前类访问。

<3> 准备 — 为静态变量内存分配

准备阶段,是为类变量(即static变量,不是成员变量)分配内存并设置初始值的阶段,这些内存都分配到方法区中。例如:public static int value  = 12;
此时赋初始值为0,而不是12. 而赋值为12是在“初始化”阶段完成。

<4> 解析 — 为普通类内存分配

解析阶段是虚拟机常量池内的符合引用(符号引用是用一组符号来描述所引用的目标对象,符号引用与虚拟机实现的内存布局无关)替换为直接引用(直接引用是直接指向目标对象的指针、相对偏移量。直接引用与虚拟机的内存布局实现相关,即同一个符合引用在不同虚拟机上翻译出来的直接引用一般不会相同)的过程。主要对如下做解析:
类、接口、成员变量、方法

有了直接引用,那么引用的目标在内存中已经存在。

<5> 初始化 — 为成员变量赋初始值

初始化阶段是执行类的构造器<cIinit>方法的过程,在下面4种情况下初始化过程会被触发:

使用new实例化对象、读取或设置类的静态变量(同时被final修饰,已在编译器把结果放入常量池的静态常量除外)、调用类的静态方法,这时会生成4条字节码指令new, getstatic, putstatic, invokestatic, 如果类没有进行过初始化,则触发其初始化。
使用java.lang.reflect包的方法对类进行反射调用的时候
当初始化一个类,发现其父类还没有初始化过,需要先初始化其父类
JVM启动时,虚拟机会先初始化包含main方法的类

对于上面准备阶段所说的
public static int value = 12;
在准备阶段完成后,value的值为0,而在初始化阶段调用了类构造器<clinit>()方法,这个阶段完成后,value的值为12.

类的构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序跟语句在源文件中的顺序一致。

3)GC
http://www.cnblogs.com/zuoxiaolong/category/508918.html
待续。。。

跟踪收集器会全局记录所有对象之间的引用状态,执行时从GC Roots的对象作为起点,从起点向下搜索所有的引用链,当一个对象到GC roots没有任何引用链时,则证明对象是不可用的。













上图中,即使Object6, Object7, Object8互相引用,但由于对于GC roots不可达,所以他们被判定为可回收的对象。

可作为GC roots的对象包括:
虚拟机栈中的引用对象
方法区中的类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI的引用对象

在通过搜索算法判断对象引用链是否可达,判定对象是否存活都与“引用”有关。

引用主要分为“强引用”、“软引用”、“弱引用”、“虚引用”。强度依次递减。
<1> 强引用:对于成员变量Object obj = new Object(); 强引用的对象永远不会被清理。在不用的时候要这样做:obj = null; 之后new Object(); 才会被GC.

对于方法内的强引用:
public void test() {
Object o = new Object();
}
当方法结束时,new Object()就会被GC.

<2> 软引用:指一些还有用,但并非必须存在的对象。当JVM内存不足时,会被GC掉。
import java.lang.ref.SoftReference;

对于成员变量:
String str=new String("abc");                                     // 强引用
SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用

对于softRef, 如果内存空间足够,则垃圾回收期不会GC它,不足时才回收它。
软引用可以用来实现内存敏感的高速缓存。

<3> 弱引用:它比软引用再弱些,被弱引用关联的对象,只能生存到下一次GC前,当GC工作时,无论内存是否足够,都会被GC掉。

String str=new String("abc");   
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
str = null;

如果这个对象仅偶尔使用一下,并且希望在使用时随时就能获取到,但又不影响对象的垃圾收集,那么就应该用弱引用。

<4> 虚引用:又称为“欢迎引用”,它是最弱的一种引用。为一个对象设置虚引用的唯一目的就是希望能在这个对象被GC回收时,收到一个系统通知。

GC算法主要有复制、标记清除、标记压缩三种算法

4)内存调优

JVM调优主要针对内存管理方面的调优,即如何控制各个代的大小,GC策略。
由于GC开始垃圾回收时会暂停应用线程,这样会严重影响系统性能,调优的目的是为了尽量降低GC所导致的应用线程暂停时间、减少Full GC次数。

最关键参数:-Xms(初始堆大小)、 -Xmx(最大堆大小) 、-Xmn(新生代大小) 、XX:SurvivorRatio(Eden, s0, s1比率)、-XX:MaxTenuringThreshold(垃圾最大年龄)、-XX:PermSize(持久代初始值)、-XX:MaxPermSize(持久带最大值)

<1> -Xms、-Xmx:通常设置为相同的值,避免运行时要不断扩展JVM内存。
<2> -XMn: 新生代大小,新生代Eden、s0、s1的比率通过-XX:ServivorRatio来控制(例如值为4,表示Eden:S0:S1 = 4:3:3)
<3> -XX:MaxTenuringThreshold: 对象在经过多少次minor GC之后进入老年代
<4> -XX:PermSize、-XX:MaxPermSize: 指定方法去的大小,通常设置为相同的值。

设置参数要考虑的问题:

<1> 避免新生代大小设置过小:问题是,一是minor GC频繁;二是可能导致minor GC对象直接进入老年代。当老年代内存不足时,会触发Full GC

<2> 避免新生代大小设置过大:一是老年代变小,可能导致Full GC频繁;二是minor GC执行回收的时间增加

<3> 避免Survivor区过大或过小:

-XX:SurvivorRatio参数的值越大,Eden区越大,minor GC次数会降低,但两块Survivor区域(S0、S1)区域变小,如果超过Survivor区内存大小的对象在minor GC后仍没被回收,那么将不会从S0, S1之间互相copy, 而是直接进入老年代。

-XX:SurvivorRatio参数值设置过小,Eden区变小,minor GC次数会增加,Survivor区变大,意味着可存储更多的在minor GC存活的对象,避免其进入老年代

<4> 合理设置对象在新生代的存活的周期

默认-XX:MaxTenuringThreshold的值为15,即如果15次还没回收掉,第16次直接移入老年代。

5)类加载器(ClassLoader)

类加载器ClassLoader用来加载class字节码到JVM中。JVM这样使用Java类:

猜你喜欢

转载自doudou-001.iteye.com/blog/2360355