初始化与清理:
垃圾清理的方式:
标记清除法: 这是垃圾收集算法中最基础的,它的思想是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是效率不高,标记和清除的效率都很低;此外会产生大量不连续的内存碎片,从而导致以后程序在分配较大对象时由于没有充足的连续内存而提前触发一次 GC 操作。
复制算法: 为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完后就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式内存的代价太高,每次基本上都要浪费一半的内存;于是将该算法进行了改进,内存区域不再是按照 1:1 去划分,而是将内存划分为 8:1:1 三部分,较大那份内存是 Eden 区,其余是两块较小的内存区叫 Survior 区,每次都会优先使用 Eden 区,若 Eden 区满则将对象复制到第二块内存区上,然后清除 Eden 区,如果此时存活的对象太多,以至于 Survivor 不够时,会将这些对象通过分配担保机制复制到老年代中。
标记整理法: 这种方法主要是为了解决标记清除法产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。
分代收集法: 现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记整理法或标记清除法。
构造器初始化:
当Java创建一个对象时,系统先为该对象的所有实例属性分配内存(前提是该类已经被加载过了),接着程序开始对这些实例属性执行初始化,其初始化顺序是:先执行初始化块或声明属性时制定的初始值,再执行构造器里制定的初始值。 在类的内部,变量定义的先后顺序决定了初始化的顺序,即时变量散布于方法定义之间,它们仍就会在任何方法(包括构造器)被调用之前得到初始化。
class Window {
Window(int maker) {
System.out.println("Window(" + maker + ")");
}
}
class House {
Window window1 = new Window(1);
House() {
System.out.println("House()");
w3 = new Window(33);
}
Window window2 = new Window(2);
void f() {
System.out.println("f()");
}
Window w3 = new Window(3);
}
public class OrderOfInitialization {
public static void main(String[] args) {
House h = new House();
h.f();
}
}
输出:
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
由输出可见,w3这个引用会被初始化两次:一次在调用构造器之前,一次在调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)。
静态数据初始化
无论创建多少个对象,静态数据都只占一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。
class Bowl {
Bowl(int maker) {
System.out.println("Bowl(" + maker + ")");
}
void f1(int maker) {
System.out.println("f1(" + maker + ")");
}
}
class Table {
static Bowl bowl1 = new Bowl(1);
Table() {
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int maker) {
System.out.println("f2(" + maker + ")");
}
static Bowl bowl2 = new Bowl(2);
}
class Cupboard {
Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard() {
System.out.println("CupBoard()");
bowl4.f1(2);
}
void f3(int maker) {
System.out.println("f3(" + maker + ")");
}
static Bowl bowl5 = new Bowl(5);
}
public class StaticInitialization {
public static void main(String[] args) {
System.out.println("created new Cupboard() in main");
new Cupboard();
System.out.println("created new Cupboard in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
输出
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
CupBoard()
f1(2)
created new Cupboard() in main
Bowl(3)
CupBoard()
f1(2)
created new Cupboard in main
Bowl(3)
CupBoard()
f1(2)
f2(1)
f3(1)
从某种程度上来看,初始化是一段固定执行的代码,它不能接受任何参数。因此初始化块对同一个类所有对象所进行的初始化处理完全相同。基于这个原因,不难发现初始化块的基本用法,如果有一段初始化处理代码对所有对象完全相同,且无须接受任何参数,就可以把这段初始化处理代码提取到初始化块中。
初始化顺序先是静态的,然后“非静态的”
public class UUIDUtil{
Mug mug1;
Mug mug2;
{
mug1=new Mug();
mug2=new Mug();
}
static {
System.out.println("UUIDUtil 静态代码块");
}
UUIDUtil(){
System.out.println("UUIDUtil 构造函数");
}
public static void main(String[] args) {
new UUIDUtil();
}
}
class Mug{
Mug(){
System.out.println("MUG(构造方法)");
}
static{
System.out.println("MUG static");
}
}
输出:
UUIDUtil 静态代码块
MUG static
MUG(构造方法)
MUG(构造方法)
UUIDUtil 构造函数
// 形式二:
public class UUIDUtil{
Mug mug1;
Mug mug2;
static {
System.out.println("UUIDUtil 静态代码块");
}
UUIDUtil(){
System.out.println("UUIDUtil 构造函数");
}
{
mug1=new Mug();
mug2=new Mug();
}
public static void main(String[] args) {
new UUIDUtil();
}
}
class Mug{
static{
System.out.println("MUG static");
}
Mug(){
System.out.println("MUG(构造方法)");
}
}
输出
UUIDUtil 静态代码块
MUG static
MUG(构造方法)
MUG(构造方法)
UUIDUtil 构造函数
上图把
static{
System.out.println("MUG static");
}
修改为{
System.out.println("MUG static");
}
UUIDUtil 静态代码块
MUG static
MUG(构造方法)
MUG static
MUG(构造方法)
UUIDUtil 构造函数
有上面可见在初始化的时候首先会调用
静态代码块--》非静态代码块-(静态代码块(只有一次),构造函数)-》构造函数,而{}包含的非静态代码块就是和构造方法一样会默认初始化。
数组初始化:
数组是一个具备相同类型的,用一个标识符名称封装到一个起的一个对象序列或基本类型数据序列。
对于数组初始化必须指定其大小,但是这样不需要比如 int[] al={1,22,3,4,5} 这种情况下存储空间的分配由编译器负责(等价于new)
数组的常用功能
数组 是一个效率高的存储和随机访问对象引用序列的方式。
常用方法:
equals():比较两个数组是否相等。
deepEquals():用于多维数组比较是否相等。
fill():填充数组。
sort():数组排序
binarySearch():用于已经排序的数组中查找元素。
toString():产生数组的toSting()表示
hashCode():产生数组的散列码。
System.arrayCopy();复制数组比用for循环速度快了许多。
数组元素的比较: 1.实现java.lang.Comparable接口,
1:所有可以 “排序” 的类都实现了java.lang.Comparable接口,Comparable接口中只有一个方法。 2:public int compareTo(Object obj) ; 该方法: 返回 0 表示 this == obj 返回整数表示 this > obj 返回负数表示 this < obj 3:实现了 Comparable 接口的类通过实现 comparaTo 方法从而确定该类对象的排序方式。
枚举类型:
可以参看:www.cnblogs.com/alter888/p/…
Q&A对于初始化与清理常见的面试题:
Q1:java 对象加载初始化的顺序?
静态代码块--》非静态代码块-(静态代码块(只有一次),构造函数)-》构造函数,而{}包含的非静态代码块就是和构造方法一样会默认初始化
Q2:如何判断一个对象是否要回收?
这就是所谓的对象存活性判断,常用的方法有两种:1.引用计数法; 2.对象可达性分析。由于引用计数法存在互相引用导致无法进行GC的问题,所以目前JVM虚拟机多使用对象可达性分析算法。从GC Roots对象计算引用链,能链上的就是存活的。
1、引用计数法
引用计数法的逻辑非常简单,但是存在问题,java并不采用这种方式进行对象存活判断。
引用计数法的逻辑是:在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++。如果一个引用关系失效则counter–。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态。
2、可达性分析算法
在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。
Q3:如何理解java中的垃圾回收机制?为什么需要垃圾回收机制?
java采用分代回收,分为年轻代、老年代、永久代。年轻代又分为E区、S1区、S2区。
到jdk1.8,永久代被元空间取代了。
年轻代都使用复制算法,老年代的收集算法看具体用什么收集器。默认是PS收集器,采用标记-整理算法。
System.gc()即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。 如果你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问,该块已分配出来的内存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越多,系统也不能再次将它分配给需要的程序,产生泄露。一直下去,程序也逐渐无内存使用,就会溢出。
Q4:java垃圾收集的方式有哪些?
上文已经讲述。
Q5:java垃圾回收机制的优缺点?
优点:
开发人员无须过多地关心内存管理,而是关注解决具体的业务。虽然内存泄漏在技术上仍然是可能出现的,但不常见。
GC 在管理内存上有很多智能的算法,它们自动在后台运行。与流行的想法相反,这些通常比手动回收更能确定什么时候是执行垃圾回收的最好时机。
缺点:
当垃圾回收发生时将影响程序的性能,显著地降低运行速度甚至将程序停止。所谓 “Stop the world” 就是当垃圾回收发生的时候应用程序的其他任务都将被冻结。对于应用程序的要求来说,这将是不可接收的,虽然 GC 调优可以最小化甚至消除这个影响。
虽然GC有很多方面可以调优,但你不能指定应用程序在何时怎样执行GC
Q6:java垃圾回收的时间有哪些?
CPU空闲时进行回收、堆内存满了后进行回收、调用System.gc()回收。
Q7:如果对象置为null,垃圾收集器是否会立即释放占用的内存?
不会。对象回收需要一个过程,这个过程中对象还能复活。而且垃圾回收具有不确定性,指不定什么时候开始回收。
Q8:什么是GC?为何有GC?
GC就是垃圾收集的意思(Gabage Collection)。我们在开发中会创建很多对象,这些对象一股脑的都扔进了堆里,如果这些对象只增加不减少,那么堆空间很快就会被耗尽。所以我们需要把一些没用的对象清理掉。jvm使用 jstat -gc 12538 5000 即会每5秒一次显示进程号为12538的java进成的GC情况.
Q9: 方法区如何判断是否需要回收?
方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件: ① 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例; ② 加载该类的ClassLoader已经被回收; ③ 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
Q10: 如果频繁老年代回收怎么分析解决?