jvm笔记一
一、java运行时数据区
1.程序计数器
程序计数器也叫做pc,可以看作是当前线程执行的字节码的行号指示器,用来指明下一条将要执行的指令,代码中的分支,循环,跳转,异常处理,线程恢复等基础功能都需要借助程序计数器来完成。该空间是线程私有的(即每个线程之间都有一个自己的程序计数器,每个程序计数器之间互不干扰)。
2.java虚拟机栈
线程在执行方法的时候会为方法创建一个栈帧,然后将其放入java虚拟机栈中,方法的执行过程就对应着一个栈帧进栈和出栈的过程,栈帧中包含的信息有方法的局部变量表,操作数栈,方法出口等信息。
3.本地方法栈
其功能与java虚拟机栈极为相似,只不过虚拟机栈的栈帧中存放的是与java方法相关的信息。而本地方法栈中的栈帧存放的是本地方法(一般指非java实现的方法)相关信息,与虚拟机栈一样,他也是线程私有的。
4.方法区
在jdk8以前,方法区常被称为永久代,从jdk8开始就使用元空间取代了,方法区中存放的是被虚拟机加载的类型信息,常量,静态信息,编译后的代码缓存等,该部分的空间大小可以固定也可以设定成可拓展(当系统中的加载的类太多的时候便会发生内存溢出的问题)。
5.堆
在java中,对象的分配都是在堆中进行的,所以他也是垃圾收集器管理的区域,为了更好的进行垃圾的回收工作,现代虚拟机的堆都是分代设计的,该区域是所有线程共享的。
二、对象分配
1.分配方式
java中对象的分配是在堆中进行的,分配方式有两种,指针碰撞和空闲列表。前者简单且效率高,但需要空间是规整的,后者可以在已使用和未使用的空间交错在一起的时候进行对象的分配。
2.内存分配的并发解决
创建对象时非常频繁的事情,在并发情况下,多个线程对同一个指针的操作是不安全的,有两种策略来解决这个问题。一种是使用同步,对分配内存空间的动作进行同步,保证在内存分配的原子性操作。另一种是为每个线程划分一个单独的区域(称为本地线程分配缓冲区),当线程需要分配内存的时候就在自己的空间中进行分配,当自己的空间用完以后需要分配新的缓冲区时才采取同步操作。
三、垃圾回收
1.判断对象是否应该回收
引用计数法:算法简单,效率高,但无法解决某些特殊情况,例如循环引用
可达性分析:以GC Roots为根节点向下遍历搜索,不能遍历到的对象就代表不可能再被使用,便可以清除(主流)。当对象被判定为不可达后不会立即被清除,而是会放入一个F-Queue队列中,并一次执行finalize方法,该方法是避免被清除的最后办法,如果再该方法中建立起了与引用链的联系,便不会被清除。(这里要注意,系统只会调用一次finalize方法,如果被执行过的对象又被判定为不可达的话便无法再逃脱被回收的命运)。
2.GC Roots
(1).虚拟机栈(本地方法表)中所引用的对象,如各个线程中调用的方法的参数、局部变量、临时变量等。
(2).静态变量
(3).常量,如字符串常量池中的引用。
(4).本地方法中JNI引用的变量。
(5).虚拟机内部的引用。
(6).被锁持有的对象。
3.垃圾收集算法
这里需要说一下当前的主流虚拟机都是基于分代设计的,一般都分为老年代和新生代,新生代中一般都是新创建的对象,而在新生代中熬过多次垃圾回收的对象就会逐步晋升到老年代中,而新生代又分为1块Eden和2块Survivor,Eden和Surivior的比例为8:1
(1)、标记—清除算法
经过可达性算法分析后标记出需要清除的对象,然后再一次性清除。算法简单但是在需要回收的对象太多时需要的时间会变长,效率会降低。且清除后有可能产生大量不可用的内存碎片。
(3)、标记—复制算法(用于新生代)
在标记清除的基础上做了改进,将内存分为A、B两部分,每次只是用其中的一部分,假设使用的是A,当这A部分用完以后再把还活着的对象复制到另B部分上,然后一次性清除A的内存,这样就不会留下大量内存碎片。
(3)、标记—整理算法(用于老年代)
将存活的对象移到一边,接下来已清除另一边