深入理解Java虚拟机

来源:互联网 发布:网络电视是什么信号源 编辑:程序博客网 时间:2024/05/02 01:40

机械出版社 《深入理解Java虚拟机》 基于JDK1.7 周志明著

在此书中,我最关心的是垃圾回收机制以及类的加载。本书中除了介绍这两部分以外,还详细分析了.class文件,有点看不懂,暂时略过。


第2章:Java内存区域与内存溢出异常

1、程序计数器:每条线程一个独立的程序计数器。如果线程正在执行的是Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器的值则为空。

此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域


2、Java虚拟机栈:也是线程私有,它生命周期与线程相同。每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。方法的执行,就是栈帧在虚拟机栈中的入栈出栈过程。局部变量表存放了编译期间可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用。

类似的 本地方法栈,针对本地方法。

3、Java堆:虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,用于存放对象实例

java堆的垃圾收集:分代收集算法

Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。

4、方法区:也是各个线程共享的内存区域。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是他却有一个别名叫做Non-Heap,目的是与Java堆区分开来。

当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常

4.1运行时常量池: 方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字变量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。运行时常量池具有动态性。


总:虚拟机遇到一条new时,检查是否已经有被加载、解析、内存过----没有的话要加载--初始化为0---init

实战: OutOfMemory异常

Java堆用于存储对象实例,只要不断地的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。

OOM异常:

1、内存泄露(Memory Leak):即对象可以回收了,但是因为什么原因导致GC不能自动回收,需要定位泄露代码的位置

2、内存溢出(Memory Overflow):即对象必须存活,这个时候就要看其他的对象是不是生命周期过长


如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。如果没有处理经验,比较难以想到。


第3章 垃圾收集器与内存分配策略

1、判断对象是否已死

方法一:引用计数法

每个对象有一个引用计数器,有地方引用就+1,引用失效,-1,计数器为0时即不再被使用

不足:难以解决对象间的循环引用问题

方法二:可达性分析算法

把一系列"GC Roots"对象作为起始点,从这些节点向下搜索,搜索走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

可以解决循环引用问题。这些对象自成循环,但是不会到GC Roots。

可作为GC Roots对象包括下面几种:

1)虚拟机栈(栈帧中的本地变量表)中引用的对象

2)方法区中类静态属性引用的对象

3)方法区中常量引用的对象

4)本地方法栈中JNI(即一般说的Native方法)引用的对象

2、关于引用

强引用:垃圾收集器永远不会回收掉被引用的对象

软引用:在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收

弱引用:被弱引用关联的对象只能生存到下一次垃圾收集发生之前

虚引用:为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到系统通知。


3、真正宣告一个对象死亡,至少要经历两次标记过程:如果在对象进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记,并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者该方法已经被调用过,则视为没有必要执行。任何一个对象的finalize()方法只会被系统自动调用一次。

4、回收方法区:

一般把方法区作为永久代,垃圾收集主要回收两部分的内容:废弃常量和无用类。判断是否为“无用类”,需要满足3个条件:

1)该类所有的实例都已经被回收

2)加载该类的ClassLoader已经被回收

3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类


5、垃圾收集算法

1)标记-清除(Mark-Sweep)算法

不足:标记和清除的效率都不高;标记清除后会产生大量不连续的内存碎片

2)复制算法(Copying)

两块等大小内存间的移动复制

不足:将内存缩小为原来的一半

3)标记-清理算法(Mark-Compact)

标记完成后,并非直接进行对象清理,而是让所有存活的对象都向一端移动。

4)分代收集算法(Generation Collection)

新生代:复制算法

老年代:标记-清理或者标记-整理


第7章 虚拟机类加载机制

前言:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

Class文件应当是一串二进制的字节流,无论以何种形式存在都可以。

类从被加载到虚拟机内存中开始,到卸载出内存为止,整个生命周期包括:加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个部分统称为连接(Linking)

接口与类真正有所区别:当一个类在初始化时,要求其父类全部都已经初始化过(当然,如果你只是使用父类的静态变量的话则不需要),但是一个接口在初始化时,并不要求其父类都完成初始化,只要在真正使用到父接口时才会初始化。

下面具体讲解类的加载过程

1、加载 :是类加载(Class Loading)过程的一个阶段。在加载阶段,虚拟机需要完成以下3件事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。


2、验证

确保Class文件的字节流中包含的信息符合当前虚拟机的要求。验证阶段是否严谨直接决定了java虚拟机是否能承受恶意代码的攻击。验证阶段会完成下面4个阶段的检验动作:文件格式验证,无数据验证,字节码验证,符号引用验证

3、准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。注意,此时没有分配实例变量,因为实例变量是分配到堆内存中。此时,初始值都为0

4、解析

解析阶段是虚拟机将常量池内的符合引用替换为直接使用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

5、初始化

初始化阶段才真正开始执行类中定义的Java程序代码,执行类构造器<clinit>()方法的过程,该方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生。虚拟机会爆炸在之类的clinit方法执行前,父类的clinit已经执行完毕。


第12章 Java内存模型与线程

每条线程都有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行,通过Save和Load操作与主内存进行数据的交换。

1、Java语言定义了5种线程状态:

1)新建(New):创建后尚未启动的线程处于这种状态

2)运行(Runable):Runable包括了操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待CPU为它分配执行时间

3)无限期等待(Waiting):处于这种状态的线程不会被分配CPU执行时间,他们要等待被其他线程显示地唤醒,以下方法会让线程陷入无限期的等待状态:

 *没有设置Timeout参数的Object.wait()方法

*没有设置Timeout参数的Thread.join()方法

*LockSupport.park()方法

4)限期等待(Timed Waiting):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显示地唤醒

5)阻塞(Blocked) 等待获取到一个排他锁

6)结束(Terminated):已终止线程的线程状态,线程已经结束执行


0 0