说下对象的建立方法?对象的内存布局?对象的访问定位?java
四种不一样的方法建立对象程序员
一、用new语句建立对象,这是最经常使用的建立对象的方式;数据库
二、调用对象的clone方法。编程
MyObject obj =new MyObject();数组
MyObject objs= obj.clone();安全
使用clone方法克隆一个对象的步骤:函数
1)被克隆的类要实现Cloneable接口;工具
2)被克隆的类要重写clone方法;布局
1 class Obj implements Cloneable{ 2 private Date birth = new Date(); 3 public Date getBirth(){ 4 return birth; 5 } 6 public void setBirth(){ 7 this.birth=birth; 8 } 9 public void changeDate(){ 10 this.birth.setMonth(4); 11 } 12 public Object clone(){ 13 Obj o = null;//o指向了复制后的新对象
14 try{ 15 o=(Obj)super.clone();//实现浅复制
16 }catch(CloneNotSupportedException e){ 17 e.printStackTrace(); 18 } 19 //实现深复制
20 o.birth = (Date)this.getBirth().clone(); 21 return o; 22 } 23 } 24 public class TestRef{ 25 public static void main(String[] args){ 26 Obj a = new Obj(); 27 Obj b = (Obj)a.clone(); 28 b.changeDate(); 29 System.out.println("a="+a.getBirth()); 30 System.out.println("b="+b.getBirth()); 31 } 32 } 33 //程序运行结果:
34 //a=Sun Jul 13 23:58:56 CST 2013
35 //b=Mon May 13 23:58:56 CST 2013
那么在编程时,如何选择使用哪一种复制方式呢?首先,检查类有无非基本类型(即对象)的数据成员。若没有,则返回super.clone()便可;如有,确保类中包含的全部非基本类型的成员变量都实现了深复制。性能
Object o = super.clone();//先执行浅复制
对每个对象attr执行如下语句:
o.attr = this.getAttr().clone();
最后返回。
须要注意的是,clone方法的保护机制在Oject中clone()是被声明为protected的,以User类为例,经过声明为protected,就能够保证只有User类里面才能“克隆”User对象。
引伸:浅复制和深复制有什么区别?
浅复制:被复制的对象的全部变量都含有与原来对象相同的值,而全部其余对象的引用仍然指向原来的对象。换而言之,浅复制仅仅复制所考虑的对象,而不复制他引用的对象。
深复制:被复制对象的全部变量都含有与原来对象相同的值,除去那些引用其余对象的变量。那些引用其余对象的变量将指向被复制的新对象,而再也不是原有的那些被引用的对象。换而言之,深复制把复制的对象所引用的对象都复制了一遍。
扩展:
原型模式主要用于对象的复制,实现一个接口(实现Cloneable接口),重写一个方法(重写Object类中的clone方法),即完成了原型模式。
原型模式中的拷贝分为“浅拷贝”和“深拷贝”:
浅拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量只复制引用,不复制引用的对象。
深拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量也进行引用对象的复制。
(Object类中clone方法只会拷贝对象中的基本数据类型的值,对于数据中、容器对象、引用对象等都不会拷贝,这就是浅拷贝。若是要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。)
原型模式的优势:
1)若是建立新的对象比较复杂时,能够利用原型模式简化对象的建立过程。
2)使用原型模式建立对象比直接new一个对象在性能上要好得多,由于Object类的clone方法是一个本地方法,它直接操做内存中的二进制流,特别是复制大对象时,性能的差异很是明显。
原型模式的适用场景:
由于以上优势,因此在须要重复地建立类似对象时能够考虑使用原型模式。好比须要在一个循环体内建立对象,假如对象建立过程比较复杂或者循环次数不少的话,使用原型模式不但能够简化建立过程,并且可使系统的总体性能提升不少。
三、运行反射手段,使用Class.forName()
MyObject object =(MyObject)Class.forName("subin.rnd.MyObject").newInstance();
四、运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。
什么是内存泄漏和内存溢出?
内存泄漏:指一个再也不被程序使用的对象或变量还在内存中占有存储空间。
两种状况:
1)在堆中申请的空间没有被释放;
2)对象已再也不被使用但还仍然在内存中保留着;
内存泄露的典型例子是一个没有重写hashCode和equals方法的Key类在HashMap中保存的状况,最后会生成不少重复的对象。全部的内存泄漏最后都会抛出OutOfMemoryError异常。
形成内存泄漏的缘由:
1)静态集合类
2)各类链接,例如数据库链接等
3)监听器
4)变量不合理的做用域
内存泄露的解决方案:
1)避免在循环中建立对象;
2)尽早释放无用对象的引用;
3)尽可能少用静态变量,由于静态变量存放在永久代(方法区),永久代基本不参与垃圾回收;
4)使用字符串处理,避免使用String,应大量使用StringBuffer,每个String对象都得独立占用内存一块区域;
在实际场景中,你怎么查找内存泄漏?
可使用 Jconsole。
内存溢出:指程序运行过程当中没法申请到足够的内存而致使的内存的一种错误。
内存溢出的几种状况(OOM异常):
OutOfMemoryError异常:
除了程序计数器外,虚拟机内存的其余几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能。
一、虚拟机栈和本地方法栈溢出
若是线程请求的栈深度大与虚拟机所容许的最大深度,将抛出StackOverflowError异常。
若是虚拟机在扩展栈时没法申请到足够的空间,则抛出OutOfMemoryError异常。
二、堆溢出
通常的异常信息:java.lang.OutOfMemoryError:Java heap spaces。
解决方案:
出现这种异常,通常手段是先经过内存影像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是不是必要的,先分清是由于内存泄漏(Memory Leak)仍是内存溢出(Memory Overflow)。
若是是内存泄露,可进一步经过工具查看泄露对象到GC Roots的引用链。因而就能找到泄露对象是经过怎样的路径与GC Roots相关联并致使垃圾收集器没法自动回收。
若是不存在泄露,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。
三、方法区溢出
异常信息:java.lang.OutOfMemoryError:PermGen space。
四、运行时常量池溢出
异常信息:java.lang.OutOfMemoryError:PermGen space。
若是要向运行时常量池中添加内容,最简单的作法就是使用String.intern()这个Native方法。该方法的做用是:若是池中已经包含一个等于此String的字符串,则返回表明池中这个字符串的String对象;不然,将此String对象包含的字符串添加到常量池中,而且返回此String对象的引用。因为常量池分配在方法区内,咱们能够经过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
致使内存溢出的缘由:
1)内存中加载的数据量过大,如一次从数据库取出过多数据;
2)集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3)代码中存在死循环或循环产生过多重复的对象实体;
4)启动参数内存值设定的大小。
内存溢出的解决方法:
第一步,修改JVM启动参数,直接增长内存。(-Xms,-Xmx参数必定不要忘记加。通常要将-Xms和-Xmx选项设置为相同,以免在每次GC后调整堆的大小;建议堆的最大值设置为可用的内存的最大值的80%)。
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其余异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
第四步,使用内存查看工具动态查看内存使用状况(Jconsole)。
如何减小GC出现的次数?(Java内存管理)
1)对象不用时最好显式置为NULL
通常而言,为NULL的对象都会被做为垃圾处理,因此将不一样的对象显式地设为NULL,有利于GC收集器断定垃圾,从而提升了GC的效率。
2)尽可能少用System.gc()
此函数建议JVM进行主GC,虽然只是建议而非必定,但不少状况下它会触发主GC,从而增长主GC的频率,也即增长了间歇性停顿的次数。
3)尽可能少用静态变量
静态变量属于全局变量,不会被GC回收,他们会一直占用内存。
4)尽可能使用StringBuffer,而不用String来累加字符串。
因为String是固定长度的字符串,累加String对象时,并不是在一个String对象中扩增,而是从新建立新的String对象,如str5=str1+str2+str3+str4,这条语句执行过程当中会产生多个垃圾对象,由于对次做“+”操做时都必须产生新String对象,但这些过渡对象对系统来讲没有实际意义的,只会增长更多的垃圾。避免这种状况能够改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
5)分散对象建立或删除的时间
集中在短期内大量建立新对象,特别是大对象,会致使忽然须要大量内存,JVM在面临这种状况时,只能进行主GC,已回收内存或整合内存碎片,从而增长主GC的频率。
集中删除对象,道理也是同样的。它使得忽然出现了大量的垃圾对象,空闲空间必然减小,从而大大增长了下一次建立新对象时强制主GC的机会。
6)尽可能少用finalize函数。由于它会加大GC的工做量,所以尽可能少用finalize方式回收资源。
7)若是须要使用常常用到的照片,可使用软引用类型,它能够尽量将图片保存在内存中,供程序调用,而不引发OutOfMemory。
8)能用基本类型如int,long,就不用Integer,Long对象
基本类型变量占用的内存资源比相应包装类对象占用的少得多,若是没有必要,最好使用基本变量。
9)增大-Xmx的值。
数组多大放在JVM老年代?永久带对象如何GC?若是想不被GC怎么办?若是想在GC中生存一次怎么办?
虚拟机提供了一个-XX:PretenureSizeThreshold参数(一般是3MB),令大于这个设置值的对象直接在老年代分配。这样作的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制。
垃圾回收不会发生在永久代,若是永久代满了或者是超过了临界值,会触发彻底垃圾回收(FullGC)。若是仔细查看垃圾回收器的输出信息,就会发现永久代也是被回收的。这就是为何正确的永久代大小对避免FullGC是很是重要的。
让对象实现finalize()方法,一次对象的自我拯救。
JVM常见的启动参数有哪些?
-Xms:设置堆的最小值。
-Xmx:设置堆的最大值。
-Xmn:设置新生代的大小。
-Xss:设置每一个线程的栈大小。
-XX:NewSize:设置新生代的初始值
-XX:MaxNewSize:设置新生代的最大值
-XX:PermSize:设置永久代的初始值
-XX:MaxPermSize:设置永久代的最大值
-XX:SurvivorRatio:年轻代中Eden区与Survivor区的大小比值
-XX:PretenureSizeThreshold:零大于这个设置值的对象直接在老年代分配。
说下几种经常使用的内存调试工具:jps、jump、jhat、jstack、jconsole、jstat
Java内存泄露的问题调查方法:jmap,jstack的使用等等。
jps:查看虚拟机进程的情况,如进程ID。
jmap:用于生成堆转储快照文件(某一时刻的)。
jhat:对于生成的堆转储快照文件进行分析。
jstack:用来生成线程快照(某一时刻的)。生成线程快照的主要目的是定位线程长时停顿的缘由(如死锁,死循环,等待I/O等),经过查看各个线程的调用堆栈,就能够知道没有响应的线程在后台作了什么或者等待什么资源。
jstat:虚拟机统计信息监视工具。如显示垃圾收集的状况,内存使用得状况。
Jconosole:主要是内存监控和线程监控。内存监控:能够显示内存的使用状况。线程监控:遇到线程停顿时,可使用这个功能。
描述Java类加载器的工做原理及其组织结构
Java类加载器的做用就是在运行时加载类。
Java类加载器基于三个机制:委托性、可见性和单一性。
一、委托机制是指双亲委派模型。当一个类加载和初始化的时候,类仅在有须要加载的时候被加载。假设你有一个应用须要的类叫做abc.class,首先加载这个类的请求由Application类加载器委托给它的父类加载器Extension类加载器,而后再委托给Bootstrap类加载器。Bootstrap类加载器会先看看rt.jar中有没有这个类,由于并无这个类,因此这个请求又回到Extension类加载器,它会查看jre/lib/ext目录下有没有这个类,若是这个类被Extension类加载器找到了,那么它将被加载,而Application类加载器不会加载这个类;而若是这个类没有被Extension类加载器找到,那么再由Application类加载器从classpath中寻找,若是没找到,就会抛出异常。
双亲委派模型机制的优势就是可以提升软件系统的安全性。由于在此机制下,用户自定义的类加载器不可能加载本应该由父类加载器加载的可靠类,从而防止不可靠的恶意代码代替由父类加载器加载的可靠代码。如java.lang.Object类老是由根类加载器加载的,其余任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类。
二、可见性原理是子类的加载器能够看见全部的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
三、单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。
Java的类加载器有三个,对应Java的三种类:
Bootstrap Loader //负责加载系统类(指的是内置类,像String)
ExtClassLoader //负责加载扩展类(就是继承类和实现类)
AppClassLoader //负责加载应用类(程序员自定义的类)
Java提供了显式加载类的API:Class.forName(classname)。