1.java的内存管理
程序计数器:
由于Java虚拟机的多线
程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内
核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条
线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。 如果线程正在执行的是一个Java方法,这个计数器
记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个
在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
Java虚拟机栈
虚拟机栈描述的是Java
方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口
等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型),它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。局部变量表所需的内存
空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改
变局部变量表的大小。
会抛出OutOfMemoryError异常;
Java堆
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例;
所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么绝对;
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”(Garbage Collected Heap,幸好国内没翻译成“垃圾堆”);
如果从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB);
会抛出OutOfMemoryError异常;
方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
对象访问
介绍完Java虚拟机的运行时数据区之后,我们就可以来探讨一个问题:在Java语言中,对象访问是如何进行的?对象访问在Java语言中无处不在,是最普通的程
序行为,但即使是最简单的访问,也会却涉及Java栈、Java堆、方法区这三个最重要内存区域之间的关联关系,如下面的这句代码:
Object obj = new Object();
假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到Java栈的本地变量表中,作为一个reference类型数据出现。而“new Object()”这部分的语
对象访问
介绍完Java虚拟机的运行时数据区之后,我们就可以来探讨一个问题:在Java语言中,对象访问是如何进行的?对象访问在Java语言中无处不在,是最普通的程
序行为,但即使是最简单的访问,也会却涉及Java栈、Java堆、方法区这三个最重要内存区域之间的关联关系,如下面的这句代码:
Object obj = new Object();
假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到Java栈的本地变量表中,作为一个reference类型数据出现。而“new Object()”这部分的语
义将会反映到Java堆中,形成一块存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现
的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实
现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
义将会反映到Java堆中,形成一块存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非
常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针
定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本
自己的理解,可以想象,方法的变量是放在栈区,用完就释放.
http://www.cnblogs.com/andy-zhou/p/5316084.html
http://www.cnblogs.com/w-wfy/p/6393992.html
day01
jre 是java 运行时环境包含程序所需的核心类库,如果写好的程序,不要开发只要运行,那么有jre就够了(包含虚拟机)
jdk 开发工具包(包含了jre)还有编译工具javac.exe,打包工具jar.exe,运行工具java.exe
2中方式配置环境变量:1.直接把bin(包含javac和java)目录配置到path下;2.配置java_home环境变量,在吧java_home配置到path下
day04
有关重载:有参数个数不同,参数类型不同(顺序不同也是)
day05
数组名是局部变量,创建在虚拟机栈中,存的是在堆去中开辟内存空间的对象的首地址
数组分动态和静态初始化
越界和空指针异常(空指针就是一个引用变量的地址是空地址,就会出现;只有引用数据类型才出现,基本数据类型没有)
java程序运行的时候,每一个方法出现都会进入虚拟机栈,以栈帧的形式索引,局部变量就在栈帧的地址范围内有效;但是方法走完就弹栈局部变量消失(main方法一样会弹栈,之后没有栈中的引用指向堆区的对象,该对象就成为了垃圾等待回收);
day06
方法区(代码仓库)里存着类的信息,.java文件编译后生成.class文件,类信息从硬盘进入内存的方法区,要用的时候引用进栈.(调用main方法是jvm做的),所谓类加载就是加载进入方法区;
所以顺序是jvm调用一个类的方法时:类加载到方法区--->类的方法进栈--->遇到变量引用--->到堆中去new出来把地址赋给栈中的成员变量
对象的创建:
都有默认的初试化值:引用类型的默认值是null;int是0;
成员变量和局部变量的区别
* A:在类中的位置不同
* 成员变量:在类中方法外
* 局部变量:在方法定义中或者方法声明上
* B:在内存中的位置不同
* 成员变量:在堆内存(成员变量属于对象,对象进堆内存)
* 局部变量:在栈内存(局部变量属于方法,方法进栈内存)
* C:生命周期不同
* 成员变量:随着对象的创建而存在,随着对象的消失而消失
* 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
* D:初始化值不同
* 成员变量:有默认初始化值---------------------------------------------->>>>>>>>>>>重要 null or 0
* 局部变量:没有默认初始化值,必须定义,赋值,然后才能使用。
当有成员和局部同名,就近原则
引用数据类型包括:数组,对象,接口,枚举;
注意------->>>>>跟主函数在一起的都加静态,不跟他在一起的不用加
封装的例子:插排
this代表当前对象的引用(不用的时候同名会因为就近原则导致赋值不成功);---->>>用来区分成员变量和局部变量重名;
day07
强引用:
软引用:用root算法,无论是否对象可达,只要内存不够就会回收
弱引用:对象可达也没有,GC下次一定回收
虚引用:无法通过虚引用获得一个对象的实例,设置虚引用的目的就是能在这个对象被收集器回收时收到一个系统通知。
静态之后的变量直接在类加载(以.class对象的形式进入)的同时进入方法区(在类空间的内容部属于类加载位置的静态区)
A:static关键字的特点
* a:随着类的加载而加载,不能用this(代表对象地址)
* b:优先于对象存在
* c:被类的所有对象共享
* 举例:咱们班级的学生应该共用同一个班级编号。
* 其实这个特点也是在告诉我们什么时候使用静态?
* 如果某个成员变量是被所有对象共享的,那么它就应该定义为静态的。
* 举例:
* 饮水机(用静态修饰)
* 水杯(不能用静态修饰)
* 共性用静态,特性用非静态
* d:可以通过类名调用
* 其实它本身也可以通过对象名调用。
* 推荐使用类名调用。
* 静态修饰的内容一般我们称其为:与类相关的,类成员
------>>>>在类加载的同时,加载了其他的方法(但是非静态的方法是信息理解为压缩文件不能直接用,而静态的方法是可以直接用的)
* 静态变量存储于方法区的静态区
* 成员变量存储于堆内存
------>>>>注意区别成员变量和局部变量的关系.
java的入口函数的参数args是接收键盘录入的;
day08---->>>>
代码块:
局部代码块:类中方法内
构造代码块(初始化块):类中方法外(没创建一次对象就会执行一次)--------------->>>>>>>>>他的作用是没必要把共同的方法放在每一个构造方法中;优先构造方法执行;
静态代码块(类初始化用):只执行一次(就是构造代码块加了static),随类加载,而加载,注意和静态方法不一样,静态方法需要调用的走,静态代码块只要类加载就执行;
继承:不要为了部分功能去继承(应该在向上抽一层);构造方法不能继承;
this可以调用子类的也可以调用父类的(其实是子类的,不过是继承下来了);super只能调用父类;
子类的所有构造方法都会先访问父类的空参构造,就是默认加了super(),这么做就是为了继承,否则父类的东西过不来;
多态的前提的是继承
day08
区别重写和重载
final修饰方法,就是不希望被子类重写
final修饰类,比如string类,不能被继承
final修饰变量,变量就变成了常量,但是是对象里面的常量,加个static就变成了全局的常量,还可以类名访问方便调用;(常量命名要大写)
----->>>>特别注意final修饰的变量一定要在对象构造完毕前初始化;可以显示初始化,也可以构造方法里初始化,只能一次.不会使用默认值;
day09
polymorphic多态:
基于多态的编译看左边,运行看右边;可以以抽象类作为左边边,右边直接实现即可;
前提:1.继承;2.方法重写;3.父类引用指向子类对象;-------->>>>>这样就产生了一个引用出现多种形态.
------------------->>>>>>>>>>>>
特别注意:在堆里创建的对象分为父类区和子类区,如果是子父类变量同名并不会被覆盖,父类引用调父类的变量,子类引用调子类的变量;但是如果是同名的方法,方法就被覆盖了,调的就是子类的;因为方法编译看的是父类(按道理),但是运行的时候动态绑定;所以在当方法是静态的时候,就不会动态绑定,还是用父类的方法,其实静态就相当于类名调用;
---------------->>>>>>>>>>>形象的理解多态:类的属性就是特征,方法就是能力.父类指向子类就是强能力(子类)乔装为统一的弱能力,特征却不会变(比如名字)
---------------->>>>>>>>>>>编译就是看父类,运行看子类,但是如果父类没有这个方法,那么呵呵,就会报错,需要转型;(就是不能使用子类特有的行为)
---------------->>>>>>>>>>>当做参数的时候多态就提高了程序的扩展性;(在医疗项目中Fragment就可以这样转统一处理,不用特有方法就直接传,用特有方法用instanceof判断后调用)
---------------->>>>>>>>>>>不能访问子类特有的属性和行为.
abstract类和方法:
抽象类特点:不能实例化(要继承重写,可以采用多态);子类一定要重写所有抽象方法(除非子类也是抽象的);也有空参构造;不可以修饰变量;方法既可以抽象,也可以非抽象;
---------------->>>>>>>>>>>前面说了多态不能使用子类特有的方法,这个时候可以在父类把特有的方法定义为抽象(不知道子类怎么执行),就由多态引出抽象;
接口:
--------------->>>>>>>>>>>特点:必须全是抽象方法;变量只能是常量(隐藏了关键字public.fianl和static);接口没有构造方法;方法(隐藏了public,abstract,光写返回值就可以);所以接口都是public(接口本来就是暴露原则)
--------------->>>>>>>>>>>接口也能使用多态指向实现者;接口和接口之间是继承继承关系可以多继承;
--------------->>>>>>>>>>>注意"抽象类和普通类一样,只是能定义抽象方法;
day10
--------------->>>>>>>>>>>
\ 本类 同一个包下(子类和无关类) 不同包下(子类) 不同包下(无关类)
private Y
默认 Y Y
protected Y Y Y
public Y Y Y Y
--------------->>>>>>>>>>>注意类只能用默认修饰符修饰:
--------------->>>>>>>>>>>当一个类中的所有方法都是静态的时候,就要把构造方法私有,不让别的类创建对象;
--------------->>>>>>>>>>>内部类,本身就是内部类的一个成员(有成员方法,成员变量) new 外部类对象.内部类对象
--------------->>>>>>>>>>>内部类调外部类的变量outer.this.变量
--------------->>>>>>>>>>>有关局部内部类(了解用):在类方法中的类
--------------->>>>>>>>>>>局部内部类访问他所在方法的变量要加final,原因就是方法弹栈变量消失,但是堆里的对象还是存在;
--------------->>>>>>>>>>>注意:匿名内部类是局部内部类的一种,必须写在方法里;
他的写法是new 接口(){实现接口的方法};整个代表实现了这个接口的对象;通常在new thread()里面要传一个实现runnable的对象,就可以传匿名内部类
当然当用接口指向是现在,当他有特有方法时没有办法调用(无法转型)
day11
object类的方法
hashcode();得到对象的哈希值
tostring():类名@哈希值(16进制)
equals方法:地址值对比但是string类重写了这个方法
--------------->>>>>>>>>>>基本数据类型都有用==比较(比较引用类型只是比较地址值),引用类型可以用equals方法(自己重写)
day12
string类被final修饰不可以被继承
""sdfsdf""这是一个对象.而这个对象是不能改变的;就比如说;
String str=new String("123123"); 这里不能改变的是"123123"这个对象,而不是str这只是个引用他可以在指向别的对象
day13
基本数据类型的包装类,用对象的方法对基本数据类型进行操作,会比较方便;(其实内部封装了方法,有对象里面就封装了方法)
day16
泛型:出现的原因是为了解决层次多了之后的类转换异常(以前是统一用object统一转型处理,容易出错);这就把运行时候的错误放到编译期发现;
--------------->>>>>>>>>>>泛型可以在类上,也可以在方法上(最好就是用类的泛型);但是注意:静态方法一定要定义自己的泛型,因为类的泛型要创建对象才确定,而静态方法类加载好他就确定了;
接口也可以添加泛型,实现的时候指定具体的类型就可以;
通配符:
List<?> list=new ArrayList<>();就是当右边返回的泛型不能确定的时候,左边可以用通配符表示;
? extends E 向E下面限定,固定上边界; ------>>>>>>相当于父类指向子类
day18
? super E
就是父类的比较器子类也可以用,固定了下边界;------>>>>>>同样相当于父类指向子类
--------------->>>>>>>>>>>需要注意,谁调用谁是泛型的E(放进去要求我有的你要有,拿出来要求你有的我全有)
day19
直接在代码中写文件名比如:"xxx.txt"他的路径就表示相对于当前包下的路径;
为什么要封装父级路径为file和子路径,而不是直接用路径的字符串呢,因为用file比字符串的功能强大;
函数file.createfile创建文件,file.mkdir创建文件夹,file.mkdirs创建多级文件夹;
--------------->>>>>>>>>>>
file的获取功能:获取绝对路径:getAbslutePath,获取构造方法里传入的路径:getPath
list:拿到多有名称的数组;listFile[]拿到该文件夹下的文件数组
day20
有关流为什么用int接收的原因是避免出现11111111 这个字节
FileOutPutStream 没有就创建,有就清空,后面会有一个boolean值表示是否追加
fis.read()表示读到的一个字节用int表示,fis.read(arr)表示读到了几个字节到arr中,没有的话就是-1
buffer不过是中间加了一个小数组来读取,包装方法,只要一个字节一个字节的读,之所以可以循环,因为把硬盘的读取,转义到了内存
close()可以刷新缓冲区之后再关闭流(最后刷新),flush()是光刷新(可以实时刷新)
--------------->>>>>>>>>>>
day21
reader和writer就是可以直接读写字符,内部都有缓冲区,记得要关流
newline是跨平台的,writer("\r\n")只适用Windows平台
设计模式1:装饰
拿到被装饰的类的引用-->在构造方法中传入-->在对于的方法中扩充被装饰的类;
破除耦合选择任意的对象包装,随意增加功能;被装饰类,跟装饰的类无关;
interface Coder {
public void code();
}
class Student implements Coder {
@Override
public void code() {
System.out.println("javase");
System.out.println("javaweb");
}
}
class HeiMaStudent implements Coder {
private Student s; //获取到被包装的类的引用
public ItcastStudent(Student s) { //通过构造函数创建对象的时候,传入被包装的对象
this.s = s;
}
@Override
public void code() { //对其原有功能进行升级
s.code();
System.out.println("数据库");
System.out.println("ssh");
System.out.println("安卓");
System.out.println(".....");
}
}
--------------->>>>>>>>>>
day22
序列流 本质就是把流放在序列流中排队,在流中遍历;
vector 有一个枚举的函数,在序列流中可以直接传入枚举的对象;
bytearrayoutputstream不用关
所谓序列化就是把对象的内容顺序存储到内存当中;就是存的过程,对应的读取就叫反序列化;
ObjecOutputStream 需要码表;
serializable 需要id的原因:存的时候有一个,取得时候按照这个来取;
拿到当前的线程对象和对象的引用名字是不同的;在那个线程调用currentThread方法,就是那那个线程的名字;
设置守护线程为了在主任务(其他非守护线程)结束的时候退出任务;
加入线程就是插队用的,要抛出一个中断异常;可以设置插队时间;
礼让线程,就是用yield让出cpu
--------------->>>>>>>>>>
注意:(非静态的)方法中的所对象是this,所以要跟同步代码块同步的话,就要用this
而在(静态方法)中不能用this(优先于对象存在)这里可以用该类的字节码对象(.class);
--------------->>>>>>>>>>
有关线程安全的问题:类中的变量加上static就是公用一个变量,但是四个线程的判断条件会冲突;
注意:有关线程加锁一定要注意锁一定要加成一样的,如果是this的话表示的是线程本身,所以采用.class对象才能保证唯一(如果用引用类型的成员变量当做锁对象,必须是静态的);
针对火车票的例子,可以把公用的资源(票数)放在runnable中这样传入到thread中就不用考虑同步的问题了
--------------->>>>>>>>>>
设计模式2:懒汉式(处理多线程的问题)和饿汉式
用static final 直接定死单例也行
懒汉式,有双重校验(锁前锁后都要校验 --第一次是效率不用每次排队,第二次防止多次创建),判空后再加锁,不要在判空前加锁(就是不要在getInstance的方法上加锁);
day24
runtime采用的是单例的模式,可以执行dos命令;
timer计时器,timertask
等待和唤醒synchronized(this)把this对象作为监视器,这个对象上所有的监视
即在同步代码块中,用哪个对象锁,就用哪个对象调用唤醒的方法;
线程组就是同意管理线程,比如可以同意设置守护线程
--------------->>>>>>>>>>
设计模式3:简单工厂:就是把对象的创建放到工厂类里去执行.----改的时候要改工厂类很不利于维护
工厂方法:就是采用多态的思想,创造一个动物工厂的接口,然后其他各个对应的工厂实现这个接口并返回对应的类,(事先要创建对应的类)----增加的时候方便;
--------------->>>>>>>>>>
day26
网络编程:tcp有客户端和服务端(有对应的ip地址和端口号),而udp只要有发有接收就行了.
--------------->>>>>>>>>>
string类型的值的确是不能修改的。
你先String s = "123";
那么在String常量池中先生成一个String对象"123",s是该对象的引用。
然后s = "456";
同样在String常量池中创建一个String对象"456",s重新指向了"456"。
"123"仍然是"123",并没有被修改。
只是s的指向发生了变化。
有关string是final为什么可以改变值;
--------------->>>>>>>>>>
有关多态的问题:“成员变量,静态方法看左边;非静态方法:编译看左边,运行看右边。”
有关throwable 包含error和exception 其中error是不能修改的(比如oom),而其中runtimeException是不用主动try/catch
--------------->>>>>>>>>>
string的加号连接对内存消耗过大,故使用stringbuffer(会在常量池中不断的创建副本)