首页 > 代码库 > JVM总结
JVM总结
第二章
一、Java运行时的数据区域:
(1)程序计数器:程序计数器用于存储正在执行的虚拟机字节码指令的地址,每个线程有自己独立的程序计数器
(2)虚拟机栈:
1)虚拟机栈是线程私有的,虚拟机栈是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量、操作数帧、动态链接、方法出口等信息。每个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
2)局部变量表存放了编译器可知的各种基本数据类型、对象引用和指向一条字节码指令的地址。
3)当线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存时,就会抛出OutOfMemoryError异常
(3)本地方法栈
本地方法栈与虚拟机栈作用十分相似,只不过本地方法栈为虚拟机执行java代码服务而本地方法栈则是为本地方法提供服务。
(4)堆
1)Java堆是虚拟机管理的最大的一块内存。Java堆被线程所共享。因为java堆是GC主要管理的部分因此也被称为GC堆。
2)Java堆可分为新生代和老年代。
3)线程还可以在堆上为自己划分出多个线程私有的分配缓冲区。
4)如果堆上有实例未能分配到内存上并且堆也无法再扩展了,将会抛出OutOfMemoryError异常
(5)方法区
方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据。
该部分的垃圾回收主要目标是对常量池的回收和对类型的卸载(1.8后将字符串常量池放到堆中了)
(6)运行时常量池
运行时常量池属于方法区的一部分。常量池用于存放编译器生成的各种字面量和符号的引用,这部分内容将在类加载后进入方法区的运行时常量池。
Java语言的动态性可以使得运行期也可以将新的常量放入常量池中。(String 的intern()方法,所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作。)
(7)直接内存
NIO类:引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteByffer对象作为这块内存的引用进行操作。
二、HotSpot虚拟机中的对象
(1)对象的创建
1)虚拟机在创建对象时是先检查该类是否加载完整,然后为该对象分配内存(内存分配有a.指针碰撞方式:基于内存是规整的;b.空闲列表:内存是零碎的),分配完内存初始化每个变量为默认值,再执行对象的初始化。
(2)对象的内存布局:
对象在内存中布局可分为3块区域:对象头、实例数据、对齐填充
对象头:存放对象的自身运行时数据如哈希码、GC分代信息、锁等。还有类型指针用于指向这个对象是哪个类的实例
(3)对象的访问定位
对象的访问分为两类:句柄和直接指针
句柄:java堆中划分出一段内存用作句柄池,引用存储的是对象句柄的地址,句柄中包含了具体对象的地址。
第三章 垃圾回收及内存管理
一、对象已死:
1)可通过引用计数器来判断对象是否有用,但该方法可能导致内存泄漏
2)可达性分析:由起始点开始向下搜索来判断哪些对象是活的哪些对象是死的。
3)引用可分为:
a)强引用
b)软引用:当内存不足时,GC对软引用进行对象回收,若回收后内存任然不足,这抛出内存溢出异常
c)弱引用: 只能存活到下一次GC时。
d)虚引用:设置虚引用的目的是在一个对象被收集器回收时收到一个系统通知。
4)finalize方法
若该对象的finalize方法没被覆盖或者被虚拟机执行过一次,那么将不执行finalize方法,finalize方法不一定执行。
二、回收方法区
方法区也是会被回收的,当没有引用指向常量时,该常量就会被清理。
当一个类被认为是无用类时才会被卸载。
三、GC回收算法
1)标记清除算法:标记出要清除的对象,然后清除。缺点是会产生大量的空间碎片
2)复制算法:两块内存,当清除时将存活的对象复制到另一块内存中。不过根据研究表明可以8:1的比例用于划分两块内存。
3)标记整理算法:如同紧凑技术
(2)垃圾收集器(未看)
(3)Full GC和Minor GC
1)Full GC/Major GC:指发生在老年代的GC,出现了Full GC一般至少伴随一次Minor GC,Full GC一般的速度一般比Minor GC慢十倍以上
2)Minor GC:指发生在新生代的垃圾收集动作。
四、内存分配策略:
1)对象优先分配到新生代Eden区中
2)大对象直接进入老年代
3)长期存活的对象将进入老年代
4)动态对象年龄判定:
如果在Survivor空间中相同年龄的所以对象大小的和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代
5)空间分配担保:
在发生Minor GC前,虚拟机检查老年代最大可用空间是否大于新生代所有对象总空间,如果是,那么这次Minor GC就是安全的。如果不是,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。若允许则检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,若大于则尝试Minor GC否则Full GC。
新生代使用复制收集算法所以用Survivor空间作为乱换备份,因为当出现大量对象在Minor GC后任然存活,则需要老年代来进行担保
第七章 虚拟机类加载机制
一、类加载的时机
(1)类加载的生命周期包括:加载、验证、准备、解析、初始化、使用、和卸载。其中验证、准备、解析三个部分统称为连接。
必须对类初始化的五种情况:
1)遇到new、getstatic、putstatic、或invokestatic这四条字节码指令时,进行初始化。
2)使用java.lang.reflect包的方法对类进行反射时
3)当初始化一个类,而其父类未初始化
4)当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类
5)1.7的动态语言支持??
(2)被动引用的例子
1)子类引用父类的静态变量不会初始化子类
2)定义数组不会导致初始化
3)单纯的引用静态常量时(编译器优化导致)
接口和类的初始化区别在于:一个类初始化要求他的父类必须都已初始化过,但一个接口则不要求,只有在真正使用到父接口的时候才会初始化
二、类加载器
(1)通过一个类的全限定名来获取描述此类的二进制字节流,这个动作放在java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”
(2)对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机的唯一性,每一个类加载器都拥有一个独立的类名称空间,不同的类加载器加载出来的类实不相等的。
(3)双亲委派模型
三种类加载器:
1)启动类加载器:加载<JAVA_HOME>\lib目录中的类
2)扩展类加载器:加载<JAVA_HOME>\lib\ext目录下的
3)应用程序加载器:加载用户路径上的类
这些类加载器之间相互配合
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,这里的父子关系由组合实现。
工作过程:一个类加载器收到类加载的请求时不是先自己加载,而是将其交由其父类进行加载,当父类无法加载时再交由自己加载,每一层都是如此。
优点:java类随着他的类加载器有了层级关系,如加载java.lang.Object类时所用的类加载器加载出来的都是相同的
第八章 虚拟机字节码执行引擎
第十二章 Java内存模型与线程
(1)Java内存模型
1)主内存与工作内存:
所有的内存模型都存储在主内存中,每条线程都有自己的工作内存,工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递只能主内存来完成
2)内存间交互操作
(2)内存间原子操作:lock、unlock、read、load、use、assign、store、write
如果把一个变量从主内存复制到工作内存就要顺序执行read和load操作,如果从工作内存复制到主内存也要顺序执行store和write操作。
(3)Volatile变量
Volatile变量两个特性:可见性、禁止指令重排序优化
不能保证原子性的场景:
1)运算结果依赖于当前值
2)变量需要与其他状态变量共同参与不变约束
(4)Java线程优先级不太靠谱:
1)依赖于操作系统对线程优先级的支持
2)优先级可能被系统改变
(5)状态转换
1)新建
2)运行
3)无限期等待:无参数的wait
4)限期等待:有参数的wait、sleep
5)阻塞:等锁
6)结束
第十三章
JVM总结