关于标记清除算法和标记整理算法:
如果清除垃圾后整理对象,则回收内存时更复杂,不移动则内存分配时更复杂,从垃圾收集的停顿时间来看,不移动对象停顿时间会更短,但是从整个程序的吞吐量来看,移动对象会更划算。所以,关注吞吐量的并行gc收集器使用标记整理算法,关注延迟的cms收集器则采用标记-清除算法的。
关于垃圾收集器:
Serial:
从jdk1.3开始出现。简单,高效,对于内存资源受限的环境,他是所有内存收集器中额外内存消耗最小的,对于单核处理器或者处理器核心较少的环境,Serial收集器由于没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率。适用于内存几十兆到百兆的情况。
ParNew收集器:
实质上是Serial收集器的多线程并行版本。可以和cms收集器一起用,cms是老年代用的收集器。ParNew收集器在单核心处理器的环境中,绝对不会比serial更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的伪双核处理环境中都不能百分之百超越Serial。
parallel Scavenge 收集器:
新生代收集器,基于标记复制算法,该收集器的特点是关注吞吐量—用户代码运行时间/(用户代码运行时间+GC时间)
SerialOld
Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
CMS收集器:
以获取最短回收停顿时间为目标的收集器,应用于互联网网站或者B/S架构的服务端,关注服务的响应速度。基于标记-清除算法。
基于标记-清除算法的收集器,都会存在内存碎片的问题。
G1收集器
开创了收集器面向局部收集的设计思路。希望实现控制停顿时间目的。G1划分了多个Region(独立区域),每个Region都可以根据需要,扮演新生代的eden,servivor或者老年代空间。G1将每个Region作为最小回收单元,因此可以避免整个Java堆中全区域的垃圾收集。让G1收集器去跟踪每个Region,并且计算需要的时间和空间大小,以便有限的时间获得尽可能高效的收集效率。默认停顿时间200MS。G1更耗费回收性能,一般6-8g以下,CMS更优。
关于对象分配和晋升问题:
对象优先在Eden分配,当Eden区没有足够空间进行分配时,虚拟机将进行一次Minor GC。大对象直接进入老年代:大对象是指需要连续内存空间的java对象。长期存活的对象将进入老年代:虚拟机给每个对象定义了一个对象年龄计数器,存储在对象头中,每经过一次minor gc,年龄+1,当年龄到达一定程度(默认15),会被晋升到老年代。如果在Survivor空间中,相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代。
JVM类加载:
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。类加载的生命周期:加载→验证→准备→解析→初始化→使用→卸载
类加载的过程:
- 1、通过一个类的全限定名来获取定义此类的二进制字节流(并没有指明必须从某个Class文件中获取,确切的说,根本没有指明要从哪里获取、如何获取—> 动态代理(运行时通过计算生成二进制字节流))
- 2、将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
- 3、在内存中生成一个代表这个类的Java.lang.CLass对象,作为方法区这个类的各种数据的访问入口。
初始化:在准备阶段,变量已经赋值成系统要求的初始零值,而在初始化阶段,程序会通过clinit方法赋值。clinit方法并不是在java中编写的代码,是javac生成的产物,有编译器自动收集类中的所有类变量的赋值动作和静态语句(static块)中的语句合并产生的。
类与类加载器:对于任意一个类,都必须由加载他的类加载器和这个类本身一起共同确立其在java虚拟机中的唯一性,比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必然不相等。
双亲委派:
站在java虚拟机的角度来看,只有两种不同的类加载器,一种是启动类加载器,该加载器通过C++实现,另一种:其他类加载器,使用Java语言实现,独立于虚拟机外部,全部都继承自java.lang.ClassLoader。三层类加载器:启动类加载器:负责加载核心的类库到虚拟机的内存中。扩展类加载器:负责加载java的扩展类库;应用程序类加载器:负责加载用户类路径上的所有类库,如果应用程序中没有自定义过自己的类加载器,该加载器就是程序中的默认类加载器。
双亲委派模型工作过程:如果一个类加载器收到了类加载的请求,他首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都如此,因此所有的加载请求最终 都应该传送给最顶层的启动类加载器中,只有当父类无法完成这个加载请求,子加载器才会尝试自己去完成加载。
硬件的效率与一致性:
基于高速缓存的存储交互很好的解决了处理器与内存速度之间的矛盾,但是它引入了一个新的问题:缓存一致性问题,在多路处理器系统中,每个处理器都有一个自己的高速缓存,而它们又共享同一主内存,这种系统称为共享内存多核系统,当多个处理器的运算任务都涉及同一块主存区域时,将可能导致各自的缓存数据不一致,为了解决一致性的问题,提出了缓存一致性协议。
java内存模型规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的数据,不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存完成。volatile:两个特性:保证此变量对所有线程的可见性,这里的可见性是指当一条线程修改了这个变量的值,新值对于其他线程来说是立即可知的,普通变量并不能做到这一点。