Java知识点
程序员文章站
2022-04-24 11:12:03
...
实例方法总是需要访问实例属性,如果实例方法不需要访问实例属性,那么它就可以使用static修饰了。
类的静态方法如果不访问本类的静态属性,那么可以把它放到其它类中,也没有什么区别。原因是它不会访问本类的任何数据(与类或对象的状态无关)。
不建议使用static属性,原因是所有对象共享同一个属性。因为可能是造成数据的争抢。而static方法比较常用。
volatile是一个类型修饰符(type specifier)。可以保证数据的完整性。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。用volatile定义之后,其实这个变量就不会因外因而变化了,可以放心使用了。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
volatile一般使用的地方
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
final关键字可以用来修饰类、属性、方法、局部变量。final是本章中唯一可以修饰局部变量的关键字。
修饰类 -- 终极类,不能被其它类继承。
修饰属性 -- 属性只能被赋值一次,并且必须在构造器结束之前赋值。
修饰方法 -- 终极方法,不能被子类覆盖。
修饰局部变量 -- 常量,只能被赋值一次。
初始化块
静态初始化块
类的加载
类的加载分为显示加载与隐示加载。隐示加载发生在类的代码的初始使用,类的隐式加载生成在类第一次第使用的时候,例如创建第一个对象,或者第一次访问类的static属性或方法时。显示加载需要使用Class类的静态方法forName()。
类的加载
类的加载分为显示加载与隐示加载。隐示加载发生在类的代码的初始使用,类的隐式加载生成在类第一次第使用的时候,例如创建第一个对象,或者第一次访问类的static属性或方法时。显示加载需要使用Class类的静态方法forName()。
无论是显示还是隐示加载,JVM都会通过classpath找到要加载的.class文件,并把.class文件的内容加载到内存。一旦一个类被加载之后,就无需再次加载。假如我们要运行MainApp类的main方法,JVM首先需要加载MainApp类,然后才会调用main方法。因为main方法是静态的,所以无需创建MainApp类的实例。
JVM加载类时,会为类的静态属性分配存储空间。然后初始化静态属性,以及执行静态初始化块。
例4-4说明了类被加载时,其静态初始化语句及静态初始化块的执行过程。
例4-4 LoaderTest.java
执行结果为:
static初始化块1。
static初始化语句
static初始化块2。
Print类的构造器会打印参数,设计该类的目的是为了测试程序的执行流程。LoaderTest类的main方法中没有任何语句,但JVM在运行该类的main方法之前需要加载该类,并且该有一个static属性,而且为该属性初始化三次。所以运行该程序可以看到加载过程的。
下面我们来分析一下程序的执行流程:
JVM加载LoaderTest类文件内容对内存。注意,这时并不会执行static块及初始化语句。这时静态属性的初始值为null。
执行静态初始化块和静态初始化语句,执行顺序按声明的顺序执行。
执行第1个静态初始化块。因为在初始化工作之间已经为静态属性分配了存储空间,所以静态初始化块中的代码并不会出错。接下来执行静态初始化语句,最后是第2个静态初始化块。
执行main方法。程序结束。
实例属性初始化块
实例属性初始化块也是类的组成部分之一,它由类直接包含。其语法为一个无名块。
{
......
}
实例化对象过程
类的构造器不只是执行其内的代码,还有很多隐示执行的内容:
new根据类图创建空白对象(因为构造器名字与类名相同),所谓空白对象是指对象属性只是被赋予了默认值。
调用父类或本类构造器。(如果没有显示调用,将隐示调用父类无参构造器。)
(无论哪个构造器都会隐示)执行实例属性的初始化语句和初始化块。
执行构造器内的其它语句(this(...)或super(...)已经执行完了)。
例4-5说明了构造器执行的过程。
例4-5 ConstructorTest.java
执行结果为:
初始化块1。
初始化语句。
初始化块2。
构造器内语句。
Print类的构造器会打印参数,设计该类的目的是为了测试程序的执行流程
ConstructorTest类的main方法中只有一条语句,创建一个A类的对象。A类中有一个实例属性为Print类型的p。该属性在两个初始化块和声明属性时的初始化语句被初始化。构造器中还有一条输出语句。
下面来分析一下程序执行流程:
执行main方法中的new A()语句。
new会根据类图创建一个空白对象。给所有实例属性赋予默认值。给p赋值为null。
调用父类无参构造器。(没有打印结果)
执行所有初始化块和初始化语句。首先执行第1个初始化块,然后执行初始化语句,最后是执行第2个初化块。执行顺序是按声明顺序执行的。
执行构造器内所有语句。
对象(不是对象引用)被创建后,最终需要销毁它,不然所有对象都常驻内存会出现内存泄漏。C++提供了析构器,当程序员不再使用某个对象时,可以调用析构器来回收对象。但是这出现了一个问题,程序员有时并不知道对象在什么时候不再被使用。Java使用自动垃圾回收机制,它使用垃圾回收器来监视用new创建的所有对象,并辨别那些不会再被引用的对象。随后,释放这些对象的内存空间,以便供其他新的对象使用。也就是说,你根本不必担心内存回收的问题。你只需要创建对象,一旦不再需要,它们就会自行消失。
垃圾回收器并不是在出现“垃圾”时立即工作,如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。原因是垃圾回收本身也会消耗很大的资源。所以你并不能确定垃圾回收器在什么时候工作,甚至你的程序到结束都没有占用大量内存,而使垃圾回收器从未执行过。
垃圾回收器只回收Java自己创建的对象所占用的内存空间,而不能回收其它语言所占用的内存空间。垃圾回收器也不能回收内存以外的资源,例如数据库连接、IO流等。这些资源你需要手动关闭。
如果有需要,你可以调用System.gc()方法请求垃圾回收器马上工作。过于频繁的调用该方法会使你的程序性能降低。
每个类都可以声明一个名字为finalize()的方法,垃圾回收器会在回收对象之前调用该对象的finalize()方法。但你不能依赖finalize()方法为你做什么,因为你无法确定垃圾回收器什么时候工作,所以你也无法确定finalize()方法方法什么时候会被调用。基本上你不需要finalize()方法做什么,只有一些特殊情况下才会用上finalize()方法。例如调用本机方法释放其它语言使用的内存。
静态引入
从JDK5.0开始,Java支持一种新的引入方式,名为静态引入。所谓静态引入就是引入某个类的静态属性或静态方法。例如:
import static java.lang.System.out;
这时,在源文件中就可以直接使用System类的静态属性out了。例如:
out.println();
而无需再写System。
System.out.println();
当然,也可以使用“*”,一次引入某个类的所有静态的东西。
import static java.lang.System.*;
这时,就可以使用System类的所有静态的东西了。如下:
out.println();
exit(0);
接口(interface)与抽象类不同,接口中的所有方法都是抽象的,而且接口没有实例属性。
使用interface声明接口,而不是class。
接口中的所有方法隐式为public、abstract的。当然,你也可以显式的使用public、abstract修饰接口中的方法。但你不能使用private、final、static等修饰符来修饰接口中的方法。
接口中的属性隐式为public、static、final的。当然,你也可以显式的使用public、static、final修饰接口中的属性。但你不能使用private、protected来修饰接口中的属性。接口中的属性必须在声明时直接初始化。例如int a = 10;
----以上引自qdmmy6老师课件
java持久化与序列化概念
持久化的对象,是已经存储到数据库或保存到本地硬盘中的对象,我们称之为持久化对象。
为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。
简单说就是对象序列化是将对象状态转换为可保持或传输的格式的过程。
什么情况下需要序列化 :
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;
对象要实现序列化,是非常简单的,只需要实现Serializable接口就可以了。
1.单件模式:
package com.zlb.demo;
public class SingletonTest {
private static volatile SingletonTest mSingletonTest = null;
private SingletonTest() {
}
public static SingletonTest getInstance() {
if(mSingletonTest == null) {
synchronized (SingletonTest.class) {
mSingletonTest = new SingletonTest();
}
}
return mSingletonTest;
}
}
2.int 和 Integer 有什么区别
1.Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。Java为每个原始类型提供了封装类。
2.原始类型封装类
boolean Boolean
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double
3.引用类型和原始类型的关系和区别
int i = 5; // 原始类型
Integer j = new Integer(10); // 对象引用
//java 1.5以后支持自动装箱所以 Integer j = 10; 也可以
1.使用原始类型无须调用 new,也无须创建对象。这节省了时间和空间。混合使用原始类型和对象也可能导致与赋值有关的意外结果。
2.原始类型是类,引用类型是对象。
3.原始类型大小比较用"==",引用类型大小比较用"equals"
4.引用类型可以被序列化,原始类型不行。
5.引用类型提供的方法可以灵活转换,可以扩展,原始类型不行
6.在集合类中只能使用引用类型,不能使用原始类型
7.原始类型没有null的概念,引用类型有,某些情况下需要辨别某个参数是否被初始化了,如果使用原始类型,那么0的值不知道是初始值还是没有初始化系统自动给的。
8.有些时候必须要用封装类,比如你要用
request.setAttribute(String key ,Object value);
这个方法时,第二个参数为Object类型,而你要放的是一个整数的时候,那就只能放Integer不能放int。
3.java 堆
1.Java中的堆空间是什么?
当Java程序开始运行时,JVM会从操作系统获取一些内存。JVM使用这些内存,这些内存的一部分就是堆内存。堆内存通常在存储地址的底层,向上排列。当一个对象通过new关键字或通过其他方式创建后,对象从堆中获得内存。当对象不再使用了,被当做垃圾回收掉 后,这些内存又重新回到堆内存中。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
2.如何增加Java堆空间
在大多数32位机、Sun的JVM上,Java的堆空间默认的大小为128MB,但也有例外。例如在32未Solaris操作系统(SPARC平台版本)上,默认的最大堆空间和起始堆空间大小为 -Xms=3670K 和 -Xmx=64M。对于64位操作系统,一般堆空间大小增加约30%。你不能任意改变堆 内存的大小,你只能在启动JVM时设定它。
3.堆和垃圾回收
我们知道对象创建在堆内存中,垃圾回收这样一个进程,它将已死对象清除出堆空间,并将这些内存再还给堆。为了给垃圾回收器使用,堆主要分成三个区域,分别叫作New Generation,Old Generation或叫Tenured Generation,以及Perm space。New Generation 是用来存放新建的对象的空间,在对象新建的时候被使用。如果长时间还使用的话,它们会被垃圾回收器移动到Old Generation(或叫Tenured Generation)。Perm space是JVM存放Meta数据的地方,例如类,方法,字符串池和类级别的详细信息。
4.Java堆中的OutOfMemoryError错误
当JVM启动时,使用了-Xms 参数设置的堆内存。当程序继续进行,创建更多对象,JVM开始扩大堆内存以容纳更多对象。JVM也会使用垃圾回收器来回收内存。当快达到-Xmx设置的最大堆内存时,如果没有更多的内存可被分配给新对象的话,JVM就会抛出java.lang.outofmemoryerror,你的程序就会down掉。在抛出 OutOfMemoryError之前,JVM会尝试着用垃圾回收器来释放足够的空间,但是发现仍旧没有足够的空间时,就会抛出这个错误。为了解决这个问题,你需要清楚你的程序对象的信息,例如,你创建了哪些对象,哪些对象占用了多少空间等等。你可以使用profiler或者堆分析器来处理 OutOfMemoryError错误。”java.lang.OutOfMemoryError: Java heap space”表示堆没有足够的空间了,不能继续扩大了。”java.lang.OutOfMemoryError: PermGen space”表示permanent generation已经装满了,你的程序不能再装载类或者再分配一个字符串了。
5.Java Heap dump
Heap dump是在某一时间对Java堆内存的快照。它对于分析堆内存或处理内存泄露和Java.lang.outofmemoryerror错误是非常有用的。在JDK中有一些工具可以帮你获取heap dump,也有一些堆分析工具来帮你分析heap dump。你可以用“jmap”来获取heap dump,它帮你创建heap dump文件,然后,你可以用“jhat”(堆分析工具)来分析这些heap dump。
6.Java堆内存(heap memory)的十个要点
1.Java堆内存是操作系统分配给JVM的内存的一部分。
2.当我们创建对象时,它们存储在Java堆内存中。
3.为了便于垃圾回收,Java堆空间分成三个区域,分别叫作New Generation, Old Generation或叫作Tenured Generation,还有Perm Space。
4.你可以通过用JVM的命令行选项 -Xms, -Xmx, -Xmn来调整Java堆空间的大小。不要忘了在大小后面加上”M”或者”G”来表示单位。举个例子,你可以用 -Xmx256m来设置堆内存最大的大小为256MB。
5.你可以用JConsole或者 Runtime.maxMemory(), Runtime.totalMemory(), Runtime.freeMemory()来查看Java中堆内存的大小。
6.你可以使用命令“jmap”来获得heap dump,用“jhat”来分析heap dump。
7.Java堆空间不同于栈空间,栈空间是用来储存调用栈和局部变量的。
8.Java垃圾回收器是用来将死掉的对象(不再使用的对象)所占用的内存回收回来,再释放到Java堆空间中。
9.当你遇到java.lang.outOfMemoryError时,不要紧张,有时候仅仅增加堆空间就可以了,但如果经常出现的话,就要看看Java程序中是不是存在内存泄露了。
10.请使用Profiler和Heap dump分析工具来查看Java堆空间,可以查看给每个对象分配了多少内存。
3.java 堆和栈
1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。
2.栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
4.java GC
Java的GC机制
1.Java中finalize()的作用一主要是清理那些对象(并非使用new)获得了一块“特殊”的内存区域。程序员可以用finalize()来操作。 程序员都了解初始化的重要性,但常常会忘记同样也重要的清理工作。毕竟,谁需要清理一个int呢?但在使用程序库时,把一个对象用完后就“弃之不顾”的做法并非总是安全的。当然,Java有垃圾回收器负责回收无用对象占据的内存资源。但也有特殊情况:假定你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它不知道该如何释放该对象的这块“特殊”内存区域,为了应对这种情况,java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储孔家,将首先调用其finalize()的方法。并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。注意这里的finalize()并不是C++里的析构.在C++中,对象一定会被销毁,而在Java里的对象却并非总是被垃圾回收(1.对象可能不被垃圾回收;2.垃圾回收并并不等于“析构”)。
2.垃圾回收只与内存有关。也就是说,使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是finalize()方法),它们也必须同内存及其回收有关。但这是否意味着要是对象中含有其他对象,finalize()就应该明确释放那些对象呢?不,无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存。这就将对finalize()的需求限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。不过,java中一切皆为对象,那这种特殊情况是怎么回事呢?由于在分配内存时可能采用了类似C语言中的做法,而非java中的通常做法。这种情况主要发生在使用“本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。在非java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非了free()函数
3.垃圾回收如何工作
“引用记数(reference counting)”是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用记数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用记数的开销不大,但需要在整个程序生命周期中持续地开销。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,就释放其占用的空间。这种方法有个缺陷,如果对象之间存在循环引用,可能会出现“对象应该被回收,但引用计数却不为零”的情况。对垃圾回收器而言,定位这样存在交互引用的对象组所需的工作量极大。引用记数常用来说明垃圾收集的工作方式,似乎从未被应用于任何一种Java虚拟机实现中。
在一些更快的模式中,垃圾回收器并非基于引用记数技术。它们依据的思想是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果你从堆栈和静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。对于发现的每个引用,你必须追踪它所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。你所访问过的对象必须都是“活”的。注意,这就解决了“存在交互引用的整体对象”的问题,这些对象根本不会被发现,因此也就被自动回收了。
在这种方式下,Java虚拟机将采用一种“自适应”的垃圾回收技术。至于如何处理找到的存活对象,取决于不同的Java虚拟机实现。有一种作法名为“停止——复制”(stop-and-copy)。这意味着,先暂停程序的运行,(所以它不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单、直接地分配新空间了。
“标记——清扫”所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清除动作才会开始。在清处过程中,没有标记的对象将被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。
“停止——复制”的意思是这种垃圾回收方式不是在后台进行的;相反,垃圾回收动作发生的同时,程序将会被暂停。在Sun 公司的文档中你会发现,许多参考文献将垃圾回收视为低优先级的后台进程,但事实上垃圾回收器并非以这种方式实现——至少Sun公司早期版本的Java虚拟机中并非如此。当可用内存数量较低时,Sun版中的垃圾回收器才会被**,同样,“标记——清扫”工作也必须在程序暂停的情况下才能进行。
如前文所述,这里讨论的Java虚拟机,内存分配单位是较大的“块”。如果对象较大,它会占用单独的块。严格来说,“停止——复制”要求你在释放旧有对象之前,必须先把所有存活对象从旧堆复制到新堆,这将导致大量内存复制行为。有了块之后,垃圾回收器在回收的时候就可以往废弃的块里拷贝对象了。每个块都用相应的“代数(generation count)”记录它是否还存活。通常,如果块在某处被引用,其代数会增加;垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清除动作——大型对象仍然不会被复制(只是其代数会增加),内含小型对象的那些块则被复制并整理。Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记——清扫”方式;同样, Java虚拟机会注意“标记——清扫”的效果,要是堆空间出现很多碎片,就会切换回“停止——复制”方式。这就是“自适应”技术。你可以给它个罗嗦的称呼:“自适应的、分代的、停止——复制、标记——清扫”式垃圾回收器。
Java虚拟机中有许多附加技术用以提升速度。尤其是与加载器操作有关的,被称为“即时”(Just-In-Time,JIT)编译的技术。这种技术可以把程序全部或部分翻译成本地机器码(这本来是Java虚拟机的工作),程序运行速度因此得以提升。当需要装载某个类(通常是在你为该类创建第一个对象)时,编译器会先找到其 .class 文件,然后将该类的字节码装入内存。此时,有两种方案可供选择。一种是就让即时编译器编译所有代码。但这种做法有两个缺陷:这种加载动作散落在整个程序生命周期内,累加起来要花更多时间;并且会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这将导致页面调度,从而降低程序速度。另一种做法称为“惰性编译(lazy uation)”,意思是即时编译器只在必要的时候才编译代码。这样,从不会被执行的代码也许就压根不会被JIT所编译。新版JDK中的Java HotSpot技术就采用了类似方法,代码每次被执行的时候都会做一些优化,所以执行的次数越多,它的速度就越快。
参考资料:《Java编程思想第四版》,《深入Java虚拟机》
5.内存泄漏
1.
2.和内存溢出的区别:
说法1:
内存溢出指你申请了10个字节的空间,但是你在这个空间写入11或以上字节的数据,就是溢出
内存泄漏指你用malloc或new申请了一块内存,但是没有通过free或delete将内存释放,导致这块内存一直处于占用状态
说法2:
内存泄漏是指分配出去的内存无法回收了
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况,是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相
应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
下面给出了一个简单的内存泄露的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须
从Vector中删除,最简单的方法就是将Vector对象设置为null。
Vector v=new Vector(10);
for (int i=1;i<100; i++)
{
Object o=new Object();
v.add(o);
o=null;
}
内存溢出是指程序要求的内存,超出了系统所能分配的范围,从而发生溢出。比如int i = -2<32> - 2<31>就会发生内存溢出。(int用4个字节来表示,而每字节有8位,故int共有2的32次方个值。)
内存溢是指在一个域中输入的数据超过它的要求而且没有对此作出处理引发的数据溢出问题,多余的数据就可以作为指令在计算机上运行。
6.HashMap和Hashtable的区别
1.继承和实现区别
Hashtable是基于陈旧的Dictionary类,完成了Map接口;HashMap是Java 1.2引进的Map接口的一个实现(HashMap继承于AbstractMap,AbstractMap完成了Map接口)。
2.线程安全不同
HashTable的方法是同步的,HashMap是未同步,所以在多线程场合要手动同步HashMap。
3.对null的处理不同
HashTable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以)。即HashTable不允许null值其实在编译期不会有任何的不一样,会照样执行,只是在运行期的时候Hashtable中设置的话回出现空指针异常。HashMap允许null值是指可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。
4.方法不同
HashTable有一个contains(Object value),功能和containsValue(Object value)功能一样。
5.HashTable使用Enumeration,HashMap使用Iterator。
6.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。---?
7.哈希值的使用不同.
1.HashTable直接使用对象的hashCode,代码是这样的:
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap重新计算hash值,而且用与代替求模:
int hash = hash(k);
int i = indexFor(hash, table.length);
static int hash(Object x) {
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
static int indexFor(int h, int length) {
return h & (length-1);
}
7.immutable类不可变类
1.不可变类,顾名思义就是说类的实例是不可被修改的。实例的信息是在创建的时候提供,并且在整个生命周期中都不可改变。Java中很多class都是immutable,像String,Integer等,它们通常用来作为Map的key.
2.其实在JDK中, String, the primitive wrapper classes, and BigInteger and BigDecimal都是不变类。那么如果让你设计和immutable的class要怎么做呢?
1.这个类不能被继承。
1.class 前面要加 final
2.如果你想在他的基础上定义一个子类也是immutable的。这时候你可以不在class 前面加final,而是在所有的方法上加final,同样可以防止子类重写它的方法。其实不用继承一样可以扩展这个类,通过composite方法(??)。不推荐允许继承immutable类
2.属性不能被改变。
3.不要提供可以改变类状态(成员变量)的方法。【get 方法不要把类里的成员变量让外部客服端引用,当需要访问成员变量时,返回成员变量的copy】
4.构造函数不要引用外部可变对象。如果需要引用外部可以变量,应该在构造函数里进行defensive copy。
3.不可变类的好处
1.不变类是线程安全的,由于不变类的状态在创建以后不再发生变化,所以它可以在线程之间共享,而不需要同步。
2.不变类的instance可以被reuse.创建类的实例需要耗费CPU的时间,当这个实例不再被引用时,将会被垃圾回收掉,这时候,又需要耗费CPU的时间。对于不变类而言,一个好处就是可以将常用的实例进行缓存,从而减少了对象的创建。举个例子,对于布尔型,
最常用的便是true and false。JDK中的Boolean类就是一个不变类,并且对这两个实例进行了缓冲。
-------------------------------------------------------------------------begin
public final class Boolean implements java.io.Serializable{
/**
* The <code>Boolean</code> object corresponding to the primitive value <code>true</code>.
*/
public static final Boolean TRUE = new Boolean(true);
/**
* The <code>Boolean</code> object corresponding to the primitive
* value <code>false</code>.
*/
public static final Boolean FALSE = new Boolean(false);
// 这个方法不会创建新的对象,而是重用已经创建好的instance
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
}
-------------------------------------------------------------------------end
3.hashCode这个方法来自于Object这个类,这个方法用来返回对象的hashCode,主要用于将对象放置到hashtable中时,来确定这个对象的存储位置。对于一个不变类的实例,它的hashCode也是不变的,所以就可以缓存这个计算的结果,来提高性能,避免不必
要的运算,JDK中的String类就是一个例子。
4.如果一个类是不变类,这个类是不是就不能有改变状态的方法呢?
答案当然是否定的,String是一个不变类,仍然有replace,replaceAll这样的方法,而String仍然是一个不变类,那是因为在这些改变状态的方法中,每次都是新创建一个String对象。如果大家理解了不变类,那也就不难理解为什么在做String的
concatenate时,应当用StringBuffer而不是用+的操作符。
5.示例:
1.//Wrong way to write a constructor:
public final class MyImmutable1 {
private final int[] myArray;
public MyImmutable1(int[] anArray) {
this.myArray = anArray; // wrong
}
public String toString() {
StringBuffer sb = new StringBuffer("Numbers are: ");
for (int i = 0; i < myArray.length; i++) {
sb.append(myArray[i] + " ");
}
return sb.toString();
}
}
// the caller could change the array after calling the constructor.
int[] array = {1,2};
MyImmutable1 myImmutableRef = new MyImmutable1(array) ;
System.out.println("Before constructing " + myImmutableRef);
array[1] = 5; // change (i.e. mutate) the element
System.out.println("After constructing " + myImmutableRef);
/*
Out put:
Before constructing Numbers are: 1 2
After constructing Numbers are: 1 5
*/
2.//Right way to write an immutable class
//Right way is to copy the array before assigning in the constructor.
final class MyImmutable2 {
private final int[] myArray;
public MyImmutable(int[] anArray) {
this.myArray = anArray.clone(); // defensive copy
}
public String toString() {
StringBuffer sb = new StringBuffer("Numbers are: ");
for (int i = 0; i < myArray.length; i++) {
sb.append(myArray[i] + " ");
}
return sb.toString();
}
}
// the caller cannot change the array after calling the constructor.
int[] array = {1,2};
MyImmutable myImmutableRef = new MyImmutable(array) ;
System.out.println("Before constructing " + myImmutableRef);
array[1] = 5; // change (i.e. mutate) the element
System.out.println("After constructing " + myImmutableRef);
Out put:
Before constructing Numbers are: 1 2
After constructing Numbers are: 1 2
6.如何正确使用String呢?
1)不要用new去创建String对象。
如果使用new去创建String,那么每次都会创建一个新对象。
public static void main(String[] args) {
String A1 = "A";
String A2 = "A"; // It won't create a new object
checkInstance(A1, A2); // Result: They are same instances
String B1 = new String("A"); // create a new object
String B2 = new String("A"); // creat a new object
checkInstance(B1, B2); // Result: They are different instances
}
private static void checkInstance(String a1, String a2) {
if (a1 == a2) {
System.out.println("They are same instances");
} else {
System.out.println("They are different instances");
}
}
2)应当用StringBuffer来做连接操作
因为String是一个不变类,那么在做连接操作时,就会创建临时对象来保存中间的运算结果,而StringBuffer是一个mutable class,这样就不需要创建临时的对象来保存结果,从而提高了性能。
类的静态方法如果不访问本类的静态属性,那么可以把它放到其它类中,也没有什么区别。原因是它不会访问本类的任何数据(与类或对象的状态无关)。
不建议使用static属性,原因是所有对象共享同一个属性。因为可能是造成数据的争抢。而static方法比较常用。
volatile是一个类型修饰符(type specifier)。可以保证数据的完整性。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。用volatile定义之后,其实这个变量就不会因外因而变化了,可以放心使用了。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
volatile一般使用的地方
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
final关键字可以用来修饰类、属性、方法、局部变量。final是本章中唯一可以修饰局部变量的关键字。
修饰类 -- 终极类,不能被其它类继承。
修饰属性 -- 属性只能被赋值一次,并且必须在构造器结束之前赋值。
修饰方法 -- 终极方法,不能被子类覆盖。
修饰局部变量 -- 常量,只能被赋值一次。
初始化块
静态初始化块
类的加载
类的加载分为显示加载与隐示加载。隐示加载发生在类的代码的初始使用,类的隐式加载生成在类第一次第使用的时候,例如创建第一个对象,或者第一次访问类的static属性或方法时。显示加载需要使用Class类的静态方法forName()。
类的加载
类的加载分为显示加载与隐示加载。隐示加载发生在类的代码的初始使用,类的隐式加载生成在类第一次第使用的时候,例如创建第一个对象,或者第一次访问类的static属性或方法时。显示加载需要使用Class类的静态方法forName()。
无论是显示还是隐示加载,JVM都会通过classpath找到要加载的.class文件,并把.class文件的内容加载到内存。一旦一个类被加载之后,就无需再次加载。假如我们要运行MainApp类的main方法,JVM首先需要加载MainApp类,然后才会调用main方法。因为main方法是静态的,所以无需创建MainApp类的实例。
JVM加载类时,会为类的静态属性分配存储空间。然后初始化静态属性,以及执行静态初始化块。
例4-4说明了类被加载时,其静态初始化语句及静态初始化块的执行过程。
例4-4 LoaderTest.java
public class LoaderTest {
static {
p = new Print("static初始化块1。");
}
public static Print p = new Print("static初始化语句。");
static {
p = new Print("static初始化块2。");
}
public static void main(String[] args) {
}
}
class Print {
public Print(String s) {
System.out.println(s);
}
}
执行结果为:
static初始化块1。
static初始化语句
static初始化块2。
Print类的构造器会打印参数,设计该类的目的是为了测试程序的执行流程。LoaderTest类的main方法中没有任何语句,但JVM在运行该类的main方法之前需要加载该类,并且该有一个static属性,而且为该属性初始化三次。所以运行该程序可以看到加载过程的。
下面我们来分析一下程序的执行流程:
JVM加载LoaderTest类文件内容对内存。注意,这时并不会执行static块及初始化语句。这时静态属性的初始值为null。
执行静态初始化块和静态初始化语句,执行顺序按声明的顺序执行。
执行第1个静态初始化块。因为在初始化工作之间已经为静态属性分配了存储空间,所以静态初始化块中的代码并不会出错。接下来执行静态初始化语句,最后是第2个静态初始化块。
执行main方法。程序结束。
实例属性初始化块
实例属性初始化块也是类的组成部分之一,它由类直接包含。其语法为一个无名块。
{
......
}
实例化对象过程
类的构造器不只是执行其内的代码,还有很多隐示执行的内容:
new根据类图创建空白对象(因为构造器名字与类名相同),所谓空白对象是指对象属性只是被赋予了默认值。
调用父类或本类构造器。(如果没有显示调用,将隐示调用父类无参构造器。)
(无论哪个构造器都会隐示)执行实例属性的初始化语句和初始化块。
执行构造器内的其它语句(this(...)或super(...)已经执行完了)。
例4-5说明了构造器执行的过程。
例4-5 ConstructorTest.java
public class ConstructorTest {
public static void main(String[] args) {
new A();
}
}
class A {
{
p = new Print("初始化块1。");
}
private Print p = new Print("初始化语句。");
public A() {
System.out.println("构造器内语句。");
}
{
p = new Print("初始化块2。");
}
}
class Print {
public Print(String s) {
System.out.println(s);
}
}
执行结果为:
初始化块1。
初始化语句。
初始化块2。
构造器内语句。
Print类的构造器会打印参数,设计该类的目的是为了测试程序的执行流程
ConstructorTest类的main方法中只有一条语句,创建一个A类的对象。A类中有一个实例属性为Print类型的p。该属性在两个初始化块和声明属性时的初始化语句被初始化。构造器中还有一条输出语句。
下面来分析一下程序执行流程:
执行main方法中的new A()语句。
new会根据类图创建一个空白对象。给所有实例属性赋予默认值。给p赋值为null。
调用父类无参构造器。(没有打印结果)
执行所有初始化块和初始化语句。首先执行第1个初始化块,然后执行初始化语句,最后是执行第2个初化块。执行顺序是按声明顺序执行的。
执行构造器内所有语句。
对象(不是对象引用)被创建后,最终需要销毁它,不然所有对象都常驻内存会出现内存泄漏。C++提供了析构器,当程序员不再使用某个对象时,可以调用析构器来回收对象。但是这出现了一个问题,程序员有时并不知道对象在什么时候不再被使用。Java使用自动垃圾回收机制,它使用垃圾回收器来监视用new创建的所有对象,并辨别那些不会再被引用的对象。随后,释放这些对象的内存空间,以便供其他新的对象使用。也就是说,你根本不必担心内存回收的问题。你只需要创建对象,一旦不再需要,它们就会自行消失。
垃圾回收器并不是在出现“垃圾”时立即工作,如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。原因是垃圾回收本身也会消耗很大的资源。所以你并不能确定垃圾回收器在什么时候工作,甚至你的程序到结束都没有占用大量内存,而使垃圾回收器从未执行过。
垃圾回收器只回收Java自己创建的对象所占用的内存空间,而不能回收其它语言所占用的内存空间。垃圾回收器也不能回收内存以外的资源,例如数据库连接、IO流等。这些资源你需要手动关闭。
如果有需要,你可以调用System.gc()方法请求垃圾回收器马上工作。过于频繁的调用该方法会使你的程序性能降低。
每个类都可以声明一个名字为finalize()的方法,垃圾回收器会在回收对象之前调用该对象的finalize()方法。但你不能依赖finalize()方法为你做什么,因为你无法确定垃圾回收器什么时候工作,所以你也无法确定finalize()方法方法什么时候会被调用。基本上你不需要finalize()方法做什么,只有一些特殊情况下才会用上finalize()方法。例如调用本机方法释放其它语言使用的内存。
静态引入
从JDK5.0开始,Java支持一种新的引入方式,名为静态引入。所谓静态引入就是引入某个类的静态属性或静态方法。例如:
import static java.lang.System.out;
这时,在源文件中就可以直接使用System类的静态属性out了。例如:
out.println();
而无需再写System。
System.out.println();
当然,也可以使用“*”,一次引入某个类的所有静态的东西。
import static java.lang.System.*;
这时,就可以使用System类的所有静态的东西了。如下:
out.println();
exit(0);
接口(interface)与抽象类不同,接口中的所有方法都是抽象的,而且接口没有实例属性。
使用interface声明接口,而不是class。
接口中的所有方法隐式为public、abstract的。当然,你也可以显式的使用public、abstract修饰接口中的方法。但你不能使用private、final、static等修饰符来修饰接口中的方法。
接口中的属性隐式为public、static、final的。当然,你也可以显式的使用public、static、final修饰接口中的属性。但你不能使用private、protected来修饰接口中的属性。接口中的属性必须在声明时直接初始化。例如int a = 10;
----以上引自qdmmy6老师课件
java持久化与序列化概念
持久化的对象,是已经存储到数据库或保存到本地硬盘中的对象,我们称之为持久化对象。
为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。
简单说就是对象序列化是将对象状态转换为可保持或传输的格式的过程。
什么情况下需要序列化 :
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;
对象要实现序列化,是非常简单的,只需要实现Serializable接口就可以了。
1.单件模式:
package com.zlb.demo;
public class SingletonTest {
private static volatile SingletonTest mSingletonTest = null;
private SingletonTest() {
}
public static SingletonTest getInstance() {
if(mSingletonTest == null) {
synchronized (SingletonTest.class) {
mSingletonTest = new SingletonTest();
}
}
return mSingletonTest;
}
}
2.int 和 Integer 有什么区别
1.Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。Java为每个原始类型提供了封装类。
2.原始类型封装类
boolean Boolean
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double
3.引用类型和原始类型的关系和区别
int i = 5; // 原始类型
Integer j = new Integer(10); // 对象引用
//java 1.5以后支持自动装箱所以 Integer j = 10; 也可以
1.使用原始类型无须调用 new,也无须创建对象。这节省了时间和空间。混合使用原始类型和对象也可能导致与赋值有关的意外结果。
2.原始类型是类,引用类型是对象。
3.原始类型大小比较用"==",引用类型大小比较用"equals"
4.引用类型可以被序列化,原始类型不行。
5.引用类型提供的方法可以灵活转换,可以扩展,原始类型不行
6.在集合类中只能使用引用类型,不能使用原始类型
7.原始类型没有null的概念,引用类型有,某些情况下需要辨别某个参数是否被初始化了,如果使用原始类型,那么0的值不知道是初始值还是没有初始化系统自动给的。
8.有些时候必须要用封装类,比如你要用
request.setAttribute(String key ,Object value);
这个方法时,第二个参数为Object类型,而你要放的是一个整数的时候,那就只能放Integer不能放int。
3.java 堆
1.Java中的堆空间是什么?
当Java程序开始运行时,JVM会从操作系统获取一些内存。JVM使用这些内存,这些内存的一部分就是堆内存。堆内存通常在存储地址的底层,向上排列。当一个对象通过new关键字或通过其他方式创建后,对象从堆中获得内存。当对象不再使用了,被当做垃圾回收掉 后,这些内存又重新回到堆内存中。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
2.如何增加Java堆空间
在大多数32位机、Sun的JVM上,Java的堆空间默认的大小为128MB,但也有例外。例如在32未Solaris操作系统(SPARC平台版本)上,默认的最大堆空间和起始堆空间大小为 -Xms=3670K 和 -Xmx=64M。对于64位操作系统,一般堆空间大小增加约30%。你不能任意改变堆 内存的大小,你只能在启动JVM时设定它。
3.堆和垃圾回收
我们知道对象创建在堆内存中,垃圾回收这样一个进程,它将已死对象清除出堆空间,并将这些内存再还给堆。为了给垃圾回收器使用,堆主要分成三个区域,分别叫作New Generation,Old Generation或叫Tenured Generation,以及Perm space。New Generation 是用来存放新建的对象的空间,在对象新建的时候被使用。如果长时间还使用的话,它们会被垃圾回收器移动到Old Generation(或叫Tenured Generation)。Perm space是JVM存放Meta数据的地方,例如类,方法,字符串池和类级别的详细信息。
4.Java堆中的OutOfMemoryError错误
当JVM启动时,使用了-Xms 参数设置的堆内存。当程序继续进行,创建更多对象,JVM开始扩大堆内存以容纳更多对象。JVM也会使用垃圾回收器来回收内存。当快达到-Xmx设置的最大堆内存时,如果没有更多的内存可被分配给新对象的话,JVM就会抛出java.lang.outofmemoryerror,你的程序就会down掉。在抛出 OutOfMemoryError之前,JVM会尝试着用垃圾回收器来释放足够的空间,但是发现仍旧没有足够的空间时,就会抛出这个错误。为了解决这个问题,你需要清楚你的程序对象的信息,例如,你创建了哪些对象,哪些对象占用了多少空间等等。你可以使用profiler或者堆分析器来处理 OutOfMemoryError错误。”java.lang.OutOfMemoryError: Java heap space”表示堆没有足够的空间了,不能继续扩大了。”java.lang.OutOfMemoryError: PermGen space”表示permanent generation已经装满了,你的程序不能再装载类或者再分配一个字符串了。
5.Java Heap dump
Heap dump是在某一时间对Java堆内存的快照。它对于分析堆内存或处理内存泄露和Java.lang.outofmemoryerror错误是非常有用的。在JDK中有一些工具可以帮你获取heap dump,也有一些堆分析工具来帮你分析heap dump。你可以用“jmap”来获取heap dump,它帮你创建heap dump文件,然后,你可以用“jhat”(堆分析工具)来分析这些heap dump。
6.Java堆内存(heap memory)的十个要点
1.Java堆内存是操作系统分配给JVM的内存的一部分。
2.当我们创建对象时,它们存储在Java堆内存中。
3.为了便于垃圾回收,Java堆空间分成三个区域,分别叫作New Generation, Old Generation或叫作Tenured Generation,还有Perm Space。
4.你可以通过用JVM的命令行选项 -Xms, -Xmx, -Xmn来调整Java堆空间的大小。不要忘了在大小后面加上”M”或者”G”来表示单位。举个例子,你可以用 -Xmx256m来设置堆内存最大的大小为256MB。
5.你可以用JConsole或者 Runtime.maxMemory(), Runtime.totalMemory(), Runtime.freeMemory()来查看Java中堆内存的大小。
6.你可以使用命令“jmap”来获得heap dump,用“jhat”来分析heap dump。
7.Java堆空间不同于栈空间,栈空间是用来储存调用栈和局部变量的。
8.Java垃圾回收器是用来将死掉的对象(不再使用的对象)所占用的内存回收回来,再释放到Java堆空间中。
9.当你遇到java.lang.outOfMemoryError时,不要紧张,有时候仅仅增加堆空间就可以了,但如果经常出现的话,就要看看Java程序中是不是存在内存泄露了。
10.请使用Profiler和Heap dump分析工具来查看Java堆空间,可以查看给每个对象分配了多少内存。
3.java 堆和栈
1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。
2.栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
4.java GC
Java的GC机制
1.Java中finalize()的作用一主要是清理那些对象(并非使用new)获得了一块“特殊”的内存区域。程序员可以用finalize()来操作。 程序员都了解初始化的重要性,但常常会忘记同样也重要的清理工作。毕竟,谁需要清理一个int呢?但在使用程序库时,把一个对象用完后就“弃之不顾”的做法并非总是安全的。当然,Java有垃圾回收器负责回收无用对象占据的内存资源。但也有特殊情况:假定你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它不知道该如何释放该对象的这块“特殊”内存区域,为了应对这种情况,java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储孔家,将首先调用其finalize()的方法。并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。注意这里的finalize()并不是C++里的析构.在C++中,对象一定会被销毁,而在Java里的对象却并非总是被垃圾回收(1.对象可能不被垃圾回收;2.垃圾回收并并不等于“析构”)。
2.垃圾回收只与内存有关。也就是说,使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是finalize()方法),它们也必须同内存及其回收有关。但这是否意味着要是对象中含有其他对象,finalize()就应该明确释放那些对象呢?不,无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存。这就将对finalize()的需求限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。不过,java中一切皆为对象,那这种特殊情况是怎么回事呢?由于在分配内存时可能采用了类似C语言中的做法,而非java中的通常做法。这种情况主要发生在使用“本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。在非java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非了free()函数
3.垃圾回收如何工作
“引用记数(reference counting)”是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用记数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用记数的开销不大,但需要在整个程序生命周期中持续地开销。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,就释放其占用的空间。这种方法有个缺陷,如果对象之间存在循环引用,可能会出现“对象应该被回收,但引用计数却不为零”的情况。对垃圾回收器而言,定位这样存在交互引用的对象组所需的工作量极大。引用记数常用来说明垃圾收集的工作方式,似乎从未被应用于任何一种Java虚拟机实现中。
在一些更快的模式中,垃圾回收器并非基于引用记数技术。它们依据的思想是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果你从堆栈和静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。对于发现的每个引用,你必须追踪它所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。你所访问过的对象必须都是“活”的。注意,这就解决了“存在交互引用的整体对象”的问题,这些对象根本不会被发现,因此也就被自动回收了。
在这种方式下,Java虚拟机将采用一种“自适应”的垃圾回收技术。至于如何处理找到的存活对象,取决于不同的Java虚拟机实现。有一种作法名为“停止——复制”(stop-and-copy)。这意味着,先暂停程序的运行,(所以它不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单、直接地分配新空间了。
“标记——清扫”所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清除动作才会开始。在清处过程中,没有标记的对象将被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。
“停止——复制”的意思是这种垃圾回收方式不是在后台进行的;相反,垃圾回收动作发生的同时,程序将会被暂停。在Sun 公司的文档中你会发现,许多参考文献将垃圾回收视为低优先级的后台进程,但事实上垃圾回收器并非以这种方式实现——至少Sun公司早期版本的Java虚拟机中并非如此。当可用内存数量较低时,Sun版中的垃圾回收器才会被**,同样,“标记——清扫”工作也必须在程序暂停的情况下才能进行。
如前文所述,这里讨论的Java虚拟机,内存分配单位是较大的“块”。如果对象较大,它会占用单独的块。严格来说,“停止——复制”要求你在释放旧有对象之前,必须先把所有存活对象从旧堆复制到新堆,这将导致大量内存复制行为。有了块之后,垃圾回收器在回收的时候就可以往废弃的块里拷贝对象了。每个块都用相应的“代数(generation count)”记录它是否还存活。通常,如果块在某处被引用,其代数会增加;垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清除动作——大型对象仍然不会被复制(只是其代数会增加),内含小型对象的那些块则被复制并整理。Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记——清扫”方式;同样, Java虚拟机会注意“标记——清扫”的效果,要是堆空间出现很多碎片,就会切换回“停止——复制”方式。这就是“自适应”技术。你可以给它个罗嗦的称呼:“自适应的、分代的、停止——复制、标记——清扫”式垃圾回收器。
Java虚拟机中有许多附加技术用以提升速度。尤其是与加载器操作有关的,被称为“即时”(Just-In-Time,JIT)编译的技术。这种技术可以把程序全部或部分翻译成本地机器码(这本来是Java虚拟机的工作),程序运行速度因此得以提升。当需要装载某个类(通常是在你为该类创建第一个对象)时,编译器会先找到其 .class 文件,然后将该类的字节码装入内存。此时,有两种方案可供选择。一种是就让即时编译器编译所有代码。但这种做法有两个缺陷:这种加载动作散落在整个程序生命周期内,累加起来要花更多时间;并且会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这将导致页面调度,从而降低程序速度。另一种做法称为“惰性编译(lazy uation)”,意思是即时编译器只在必要的时候才编译代码。这样,从不会被执行的代码也许就压根不会被JIT所编译。新版JDK中的Java HotSpot技术就采用了类似方法,代码每次被执行的时候都会做一些优化,所以执行的次数越多,它的速度就越快。
参考资料:《Java编程思想第四版》,《深入Java虚拟机》
5.内存泄漏
1.
2.和内存溢出的区别:
说法1:
内存溢出指你申请了10个字节的空间,但是你在这个空间写入11或以上字节的数据,就是溢出
内存泄漏指你用malloc或new申请了一块内存,但是没有通过free或delete将内存释放,导致这块内存一直处于占用状态
说法2:
内存泄漏是指分配出去的内存无法回收了
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况,是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相
应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
下面给出了一个简单的内存泄露的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须
从Vector中删除,最简单的方法就是将Vector对象设置为null。
Vector v=new Vector(10);
for (int i=1;i<100; i++)
{
Object o=new Object();
v.add(o);
o=null;
}
内存溢出是指程序要求的内存,超出了系统所能分配的范围,从而发生溢出。比如int i = -2<32> - 2<31>就会发生内存溢出。(int用4个字节来表示,而每字节有8位,故int共有2的32次方个值。)
内存溢是指在一个域中输入的数据超过它的要求而且没有对此作出处理引发的数据溢出问题,多余的数据就可以作为指令在计算机上运行。
6.HashMap和Hashtable的区别
1.继承和实现区别
Hashtable是基于陈旧的Dictionary类,完成了Map接口;HashMap是Java 1.2引进的Map接口的一个实现(HashMap继承于AbstractMap,AbstractMap完成了Map接口)。
2.线程安全不同
HashTable的方法是同步的,HashMap是未同步,所以在多线程场合要手动同步HashMap。
3.对null的处理不同
HashTable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以)。即HashTable不允许null值其实在编译期不会有任何的不一样,会照样执行,只是在运行期的时候Hashtable中设置的话回出现空指针异常。HashMap允许null值是指可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。
4.方法不同
HashTable有一个contains(Object value),功能和containsValue(Object value)功能一样。
5.HashTable使用Enumeration,HashMap使用Iterator。
6.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。---?
7.哈希值的使用不同.
1.HashTable直接使用对象的hashCode,代码是这样的:
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap重新计算hash值,而且用与代替求模:
int hash = hash(k);
int i = indexFor(hash, table.length);
static int hash(Object x) {
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
static int indexFor(int h, int length) {
return h & (length-1);
}
7.immutable类不可变类
1.不可变类,顾名思义就是说类的实例是不可被修改的。实例的信息是在创建的时候提供,并且在整个生命周期中都不可改变。Java中很多class都是immutable,像String,Integer等,它们通常用来作为Map的key.
2.其实在JDK中, String, the primitive wrapper classes, and BigInteger and BigDecimal都是不变类。那么如果让你设计和immutable的class要怎么做呢?
1.这个类不能被继承。
1.class 前面要加 final
2.如果你想在他的基础上定义一个子类也是immutable的。这时候你可以不在class 前面加final,而是在所有的方法上加final,同样可以防止子类重写它的方法。其实不用继承一样可以扩展这个类,通过composite方法(??)。不推荐允许继承immutable类
2.属性不能被改变。
3.不要提供可以改变类状态(成员变量)的方法。【get 方法不要把类里的成员变量让外部客服端引用,当需要访问成员变量时,返回成员变量的copy】
4.构造函数不要引用外部可变对象。如果需要引用外部可以变量,应该在构造函数里进行defensive copy。
3.不可变类的好处
1.不变类是线程安全的,由于不变类的状态在创建以后不再发生变化,所以它可以在线程之间共享,而不需要同步。
2.不变类的instance可以被reuse.创建类的实例需要耗费CPU的时间,当这个实例不再被引用时,将会被垃圾回收掉,这时候,又需要耗费CPU的时间。对于不变类而言,一个好处就是可以将常用的实例进行缓存,从而减少了对象的创建。举个例子,对于布尔型,
最常用的便是true and false。JDK中的Boolean类就是一个不变类,并且对这两个实例进行了缓冲。
-------------------------------------------------------------------------begin
public final class Boolean implements java.io.Serializable{
/**
* The <code>Boolean</code> object corresponding to the primitive value <code>true</code>.
*/
public static final Boolean TRUE = new Boolean(true);
/**
* The <code>Boolean</code> object corresponding to the primitive
* value <code>false</code>.
*/
public static final Boolean FALSE = new Boolean(false);
// 这个方法不会创建新的对象,而是重用已经创建好的instance
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
}
-------------------------------------------------------------------------end
3.hashCode这个方法来自于Object这个类,这个方法用来返回对象的hashCode,主要用于将对象放置到hashtable中时,来确定这个对象的存储位置。对于一个不变类的实例,它的hashCode也是不变的,所以就可以缓存这个计算的结果,来提高性能,避免不必
要的运算,JDK中的String类就是一个例子。
4.如果一个类是不变类,这个类是不是就不能有改变状态的方法呢?
答案当然是否定的,String是一个不变类,仍然有replace,replaceAll这样的方法,而String仍然是一个不变类,那是因为在这些改变状态的方法中,每次都是新创建一个String对象。如果大家理解了不变类,那也就不难理解为什么在做String的
concatenate时,应当用StringBuffer而不是用+的操作符。
5.示例:
1.//Wrong way to write a constructor:
public final class MyImmutable1 {
private final int[] myArray;
public MyImmutable1(int[] anArray) {
this.myArray = anArray; // wrong
}
public String toString() {
StringBuffer sb = new StringBuffer("Numbers are: ");
for (int i = 0; i < myArray.length; i++) {
sb.append(myArray[i] + " ");
}
return sb.toString();
}
}
// the caller could change the array after calling the constructor.
int[] array = {1,2};
MyImmutable1 myImmutableRef = new MyImmutable1(array) ;
System.out.println("Before constructing " + myImmutableRef);
array[1] = 5; // change (i.e. mutate) the element
System.out.println("After constructing " + myImmutableRef);
/*
Out put:
Before constructing Numbers are: 1 2
After constructing Numbers are: 1 5
*/
2.//Right way to write an immutable class
//Right way is to copy the array before assigning in the constructor.
final class MyImmutable2 {
private final int[] myArray;
public MyImmutable(int[] anArray) {
this.myArray = anArray.clone(); // defensive copy
}
public String toString() {
StringBuffer sb = new StringBuffer("Numbers are: ");
for (int i = 0; i < myArray.length; i++) {
sb.append(myArray[i] + " ");
}
return sb.toString();
}
}
// the caller cannot change the array after calling the constructor.
int[] array = {1,2};
MyImmutable myImmutableRef = new MyImmutable(array) ;
System.out.println("Before constructing " + myImmutableRef);
array[1] = 5; // change (i.e. mutate) the element
System.out.println("After constructing " + myImmutableRef);
Out put:
Before constructing Numbers are: 1 2
After constructing Numbers are: 1 2
6.如何正确使用String呢?
1)不要用new去创建String对象。
如果使用new去创建String,那么每次都会创建一个新对象。
public static void main(String[] args) {
String A1 = "A";
String A2 = "A"; // It won't create a new object
checkInstance(A1, A2); // Result: They are same instances
String B1 = new String("A"); // create a new object
String B2 = new String("A"); // creat a new object
checkInstance(B1, B2); // Result: They are different instances
}
private static void checkInstance(String a1, String a2) {
if (a1 == a2) {
System.out.println("They are same instances");
} else {
System.out.println("They are different instances");
}
}
2)应当用StringBuffer来做连接操作
因为String是一个不变类,那么在做连接操作时,就会创建临时对象来保存中间的运算结果,而StringBuffer是一个mutable class,这样就不需要创建临时的对象来保存结果,从而提高了性能。
上一篇: java 知识点