Java内存区域划分
1、程序计数器
线程私有,当前线程执行的行号指示器,指向当前线程执行的虚拟机字节码地址,线程的恢复,跳转等都需要用到它
2、Java虚拟机栈
线程私有,虚拟机栈描述的是Java内存模型,用于存储局部变量、操作数栈、动态连接、方法出口等信息;Java每一个方法的执行都对应一个栈帧的出栈和入栈的过程
可通过-Xss参数来设定栈容量
3、本地方法栈
线程私有,类似于Java虚拟机栈,用于执行虚拟机用到的native方法,与Java虚拟机栈原理一致,只是它们服务的对象不一样
4、Java堆
线程共享,Java堆在虚拟机启动的时候被创建,此区域的唯一目的就是存放对象,Java虚拟机规范规定:所有的对象实例和数组都要在堆内存分配
为了方便内存的回收,将Java堆分为老年代和新生代,方便采用分代收集算法回收内存
虚拟机启动时,可通过-Xmx(最大值)和-Xms(最小值)两个变量来控制堆的大小
5、方法区
线程共享,用于存放被虚拟机加载的类信息、常量、静态变量、即时编译生成的代码等。
该区域的垃圾回收主要针对常量池的回收和类型的卸载,但总是令人难以满意,因为类的卸载条件非常苛刻
通过-XX:PermSize和-XX:MaxPermSize来设置方法区的容量
6、运行常量池
方法区的一部分,主要用于存放编译期生成的各种字面量和符号引用
7、直接内存
直接内存不属于虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,直接内存不会收到Java堆大小的限制
直接内存的作用是:使得Java可以通过DirectByteBuffer对象直接操作这块内存,避免Java堆和Native堆中来回的数据复制,从而提高性能
可通过-XX:MaxDirectMemorySize来指定直接内存的大小
对象创建过程
1、虚拟机遇到一条new指令时,先去检查这个指令的参数能否在常量池中找到对应类的符号引用,并检查这个类的符号引用是否被加载、解析、和初始化过,如果没有,就执行加载和初始化过程
2、在类加载检查通过后,虚拟机将为新生对象分配内存。
分配内存有两种方法:一、指针碰撞。如果内存中已经使用的内存放在一边,未使用过的内存放在一边,中间用一个指针作为分界点的指示器,则将指针向未使用内存一方偏移对象大小的距离,称为指针碰撞。二、空闲列表法。如果内存不连续,则虚拟机中会维护一个空闲空间内存地址的列表,虚拟机会在这个列表中找到一块足够大的内存分配给新的变量,称为空闲列表法。
那如何保证内存分配的安全性呢?一、对内存空间的分配动作采用同步处理。事实上,虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。二、对每个线程,在Java堆中预先分配一小块内存,称为本地线程分配缓冲TLAB,以此来隔离各个线程的内存分配,使得它们互不干扰
3、内存分配完成后,虚拟机将分配到的内存空间都初始化为零
4、对对象进行必要的设置。如:类的元数据、对象的哈希码、对象的GC分代信息等
5、调用对象的init方法,执行初始化,完成对象创建
对象的内存布局
1、在HotSpot虚拟机中,对象分内存分为:对象头、实例数据和对齐填充三个部分
对象头包含两个部分,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、偏向线程ID、偏向时间戳等;
第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过该指针确定对象是哪个类的实例
实例数据用于存储类真正的有效信息,即程序代码中定义的各种类型的字段内容
对齐填充,这部分并不是必然存在,也没有特别意义,仅仅是起着占位符的作用
对象访问
Java程序是通过栈上的reference数据来操作具体对象的。目前主要的对象访问方式有两种:
一、句柄访问。reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据、类型数据各自的具体地址信息
二、直接访问。reference中直接存储对象的地址
如何判断对象是否存活
1、引用计数法:给对象添加一个引用计数器,每当一个地方引用它时,计数器加1;当引用失效时,计数器减1,当计数器为0,表示该对象不再被使用,应该回收。
优点是:实现简单,判断效率高。缺点是:很难解决对象之间互相循环引用的问题。例如objA.instance=objB;objB.instance=objA;其他地方再无引用这两个变量,但是因为它们互相引用着对方,因而计数器都不为零,导致GC无法回收它们
实际上,Java的大多虚拟机实现都没有采用上面这种方法
2、可达性分析法:通过一系列称为GC Roots的对象作为起点,从这些起点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链,证明该对象不可用,应该被回收
Java中的引用
引用:如果reference类型的数据中存储的数值代表另一块内存中的起始地址,就称这块内存代表着一个引用
引用分为强引用、软引用、弱引用、虚引用四种
强引用:如果对象存在强引用,就不能被垃圾收集器回收,入Object obj = new Object();
软引用:用来描述一些还有用但并非必须的对象。在系统发生内存溢出之前会将这些对象回收
弱引用:用来描述一些非必须的对象,比软引用更弱一些。被弱引用关联的对象只能存活到下一次垃圾回收之前
虚引用:也称为幽灵引用,是最弱的一种引用关系。一个对象有虚引用,完全不会对其生命周期产生任何影响,也不能通过虚引用来创建对象实例,为对象设置虚引用的目的是在对象被垃圾回收之前获得一个通知
垃圾回收算法
为什么要了解垃圾回收?当要排查各种内存泄漏、内存溢出时,当垃圾回收变成系统高并发的瓶颈时,我们需要对这些自动化技术进行监控
1、标记-清除算法
基本思想:首先标记出需要回收的对象,然后在标记完成之后统一回收被标记的对象。
不足之处:一、标记和清除两个过程效率都不高;二、空间问题,标记清除后会产生大量不连续的空间,形成空间碎片,导致后面的大对象可能无法找到一个足够大的连续内存,从而触发提前GC
2、复制算法
基本思想:将内存分为大小相等的2块,当一块内存用完,就将还存活的对象复制到另一块内存,再把使用过的内存一次全部清理掉
优点:不存在内存碎片;内存分配时只需要移动指针即可,实现简单,运行高效
缺点:内存浪费,内存空间只有一半的利用率;在对象存活率高时,复制效率将会比较低
主要用于新生代,因为新生代中,正常80%的对象都是朝生夕死的,所以可以采用8:1:1的比例来划分内存,用两个较小的内存来存放第一次回收时没有被回收掉的对象
3、标记-整理算法
基本思想:类似于标记-清理算法;先将可回收的对象标记出来,然后将所有存活的对象都复制到另外一边,之后清理掉另一边的所有内存
4、分代收集算法
基本思想:根据对象存活周期的不同将内存划分为几块,然后对不同的区域采用不同的收集算法。
通常将内存划分为新生代和老年代。新生代中,每次垃圾回收时都会有大量的对象死去,可选用复制算法,每次都可以用少量的复制成本完成收集;对于老年代,对象存活率高,可采用标记-清理算法或则标记-整理算法
HotSpot的关键概念
1、枚举根节点
在HotSpot中,使用了一种叫做OopMap的数据结构来实现准确实GC,完成GC Root 枚举
2、安全点
在进行GC时会挂起所有线程,正在执行的线程挂起后,GC完成后可正确恢复的点
3、安全区域
线程执行到该区域中的任务位置开始GC而被挂起后,线程都可正确恢复的区域,被称为安全区
常见的收集器
1、Serial收集器。单线程,常用于新生代,特点,执行垃圾回收时挂起所有其他线程
2、ParNew收集器。Serial的多线程版本
3、CMS收集器。
关于垃圾回收
对象被创建时分配到eden区,如果eden区没有足够的内存,则触发一次MinorGC,如果还是无法获取足够的内存分配对象,则会触发Full GC,通常FullGC的速度会比Minor GC的速度慢上很多
走过一次Minor GC,对象的年龄增加1岁,默认超过15岁的对象会被移动到老年代,可以通过设置-XX:MaxTenuringThreshold来设置阈值,
对于大对象,可以设置-XX:pretenureSizeThreshold参数来规定大于某个值得大对象直接分配到老年代,来避免Eden区和Survivor区之间频繁的大量内存复制
PretenureSizeThreshold这个参数只对Serial和ParNew这两个收集器有效
空间分配担保:就是垃圾手机器为了避免频繁的Full GC,在进行一次Minor GC时,老年代会为新生代做一次担保,保证如果回收后新生代仍不满足内存需求的情况下将一部分新生代的对象移动到老年代,
避免频繁GC
常用的JDK命令行工具
jps Java虚拟机进程状态工具
显示指定系统内所有的Hotspot虚拟机进程,有四个可选参数:
-q 只输出虚拟机唯一主机id(LVMID),省略主类名称
-m 输出虚拟机启动时传递给主类main函数的参数
-l 输出主类的全名,如果启动时是jar包,输出jar包路径
-v 输出虚拟机启动时JVM参数
jstat 虚拟机统计信息监控工具
用于收集Hotspot虚拟机各方面的运行数据
jinfo Java配置信息工具
显示虚拟机配置信息
实时查看和调整虚拟机的各项参数
jmap Java内存映像工具
生成虚拟机内存快照
jhat 虚拟机转储快照分析工具用于分析heapdump文件
用于分析heapdump文件
jstack Java堆栈跟踪工具
显示虚拟机当前时刻的线程快照
线程快照就是当前虚拟机内每一条线程正在执行方法堆栈的集合,生成快照的目的是定位线程出现长时间停顿的原因
线程停顿的时候可以通过jstack来查看各线程的调用堆栈
有以下几个可选参数:
-F 当前正常输出的请求不被响应,强制输出线程堆栈
-l 除堆栈外,显示关于锁的附加信息
-m 如果调用到本地方法,可以显示C/C++的堆栈
类的加载机制
类的加载时指虚拟机把描述类的class文件加载到虚拟机内存,并对数据进行校验、转换解析和初始化,最终形成虚拟机可以直接使用的Java类型的过程