Thinking in Java 学习笔记

Java内存

JVM内存空间划分:

方法区(Method Area):方法区存放类信息(类名、修饰)、类的静态变量、final常量和方法信息。在Hotspot中,方法区对应的是持久代(permanent generation)。方法区的垃圾收集主要针对常量池回收和对已加载类的卸载

堆区(Heap):堆区存放对象实例及数组,通过new创建的对象都存储在堆区,堆区在虚拟机启动的时候被创建,并被所有的线程所共享。堆区是Java GC机制管理的主要内存区域

虚拟机栈(JVM Stack):每个线程对应一个虚拟机栈,一个线程的每个方法在执行时都会创建一个栈帧,栈帧中存储局部变量表、方法出口、操作站、动态链接。局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等

本地方法栈(Native Method Stack):本地方法栈用于支持native方法的执行,存储每个本地方法调用的状态,本地方法栈和虚拟机方法栈运行机制一致。在很多虚拟机中(如Sun的JDK默认的Hotspot虚拟机),会将本地方法栈与虚拟机栈放在一起使用

程序计数器(Program Counter Register):程序计数器用于指示当前线程所执行的字节码执行到了第几行,它是一个比较小的内存区域。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。

【注】java 8之后,方法区改为元数据区,并且移除了持久代。元数据保存在本地内存,被称为metaspace

内存分代回收:当对象分配越多,对象列表越来越大时,GC扫描和移动耗时增加,而大部分对象存活时间较短,少部分对象存活时间较长,所以采用分代回收的策略实际是为了提高GC效率

年轻代(Young Generation):对象在被创建时,内存首先在年轻代进行分配(大对象可以直接在老年代分配),年轻代由Eden Space和两块相同大小的SurvivorSpace组成。年轻代的Eden区内存是连续的,所以其分配会非常快;同样Eden区的回收也非常快。年轻代需要回收时触发Minor GC

扫描二维码关注公众号,回复: 2519488 查看本文章

老年代(Old Generation):老年代用于存放在年轻代中经过多次垃圾回收仍然存在的对象,例如缓存。除此之外,还有两种情况会在老年代直接分配内存:一种是大对象,另外一种是大的数组对象(数组中无引用外部对象)。老年代回收时触发Major GC

持久代(Permanent Generation):持久代对应虚拟机内存中的“方法区”,即主要保存类信息、类的静态变量、方法信息等,由于这些信息在运行过程中基本上会一直使用,所以针对持久代的内存回收效率很低

内存回收的几种算法:

复制(Copying):采用的方式为从根集合扫描出存活的对象,并将其复制到一块空闲空间,当待回收控件中存活的对象较少时,该算法执行效率较高(年轻代的Eden区域即采用此种算法)

标记-清除(Marking -Deleting):采用的方式为从根集合开始扫描,标记存活的对象,然后扫描控件中未标记的对象并进行清除。标记-清除算法不需要移动对象,在控件对象存活较多的情况下比较高效,但也因此或造成内存碎片

标记-压缩(Marking - Compact):标记-压缩算法前面部分的处理方式同标记-清除算法,只是在对象清除后,会再把存活的对象移动到空闲空间,从而避免了内存碎片的问题,当然由于增加了一步移动,所以成本会比标记-清除算法高

Java内存模型(JMM:Java MemoryMode):

l  Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量等细节(这里的变量不是值java编程中的变量,而是实例字段,静态字段和构成数组对象的元素)

l  调用栈和本地变量存放在线程栈上,对象存放在堆上。所有的变量都存储在主内存(Main Memory)中,每个线程还有自己的工作内存(Working Memory),线程的工作内存保存了该线程使用变量的主内存副本拷贝

l  从更低的层次来说,主内存就是硬件的内存,而为了获取更好的运行速度,虚拟机及硬件系统可能会让工作内存优先存储于寄存器和高速缓存中

l  线程对所有变量的操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量,不同线程之间无法互相访问彼此线程工作内存中的变量,线程之间的值传递都需要通过主内存完成

l  线程见数据交换过程:1)线程S1将工作内存中的变量更新到主内存,2)线程S2读取更新后的主内存变量并复制到S2的工作内存中

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:

·       lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。

·       unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

·       read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用

·       load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

·       use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。

·       assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

·       store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。

·       write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

 

Java中的volatile关键字:volatile关键字可以保证直接从主存中读取一个变量,如果这个变量被修改后,总是会被写回到主存中去。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是:volatile的特殊规则保证了新值能立即同步到主内存,以及每个线程在每次使用volatile变量前都立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

 

Java中的synchronized关键字:同步块的可见性是由“如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值”、“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store和write操作)”这两条规则获得的。

初始化和清理

在Java中,“初始化”和“创建”捆绑在一起,两者不能分离。

构造方法是一种特殊类型的方法,它没有返回值(与返回void不同)。

方法重载(overload):在同一个类中或子类中,名字相同而参数不同的方法

l  每个重载的方法都必须有一个独一无二的参数类型列表(参数个数、类型或顺序不同)

l  重载的方法可以改变访问修饰符

l  重载的方法可以声明新的或更广的异常检查

l  重载的方法可以有不同的返回值类型,但是不能仅仅通过返回值类型来区分重载方法

方法重写(override):重写是子类对父类方法的实现过程进行重新编写,返回值和参数不变

l  参数列表必须与被重写方法完全相同

l  返回类型必须与被重写方法的返回类型完全相同

l  访问权限不能比父类中被重写方法的访问权限更小

l  Final方法和static方法不能被重写

l  重写的方法可以抛出任何非强制性异常,但是不能抛出任何新的强制性异常(反之可以)

Static关键字:

l  可以在没有创建任何对象的情况下,仅仅通过类本身来调用static方法

l  Static方法的内部不能调用非static方法,反之可以

l  Static方法的内部不能使用非静态的static变量

l  Static方法内部不能使用this关键字(this关键字只能在方法内部使用,表示对“调用此方法的那个对象”的引用,但是static方法可以通过类本身来调用,所以static方法中不能使用this关键字)

l  Static 方法无法具有多态属性

垃圾回收:使用垃圾回收的原因是回收程序不再使用的内存

https://blog.csdn.net/suifeng3051/article/details/48292193

finalize()方法:finalize()方法主要用来清理本地方法所分配的内存空间。本地方法指在Java中调用的非Java方法。

成员初始化:

l  类的成员,且为基本类型时,如果不对其进行初始化,该成员将获得一个初始值

l  类的成员,不管是基本类型还是对象引用,当在构造方法中使用时,如果不对其进行初始化,则该成员会获得一个初始值(对象引用的初始值为null)

l  除上述两种情况之外,所有成员都必须进行初始化,否则编译报错

静态变量初始化:

l  Static关键字不能应用于局部变量

l  无论创建多少个对象,静态数据都只占用一份存储区

l  静态变量的初始化只有在必要的时候才会进行,且只执行一次

访问权限控制

权限从大到小依次为:public、protected、包访问权限(没有关键字,默认的访问权限)和private

l  每个编译单元(文件)都只能有一个public类,如果有一个以上public类,编译报错

l  Public类的名称必须和包含该编译单元的文件名完全一致,否则编译报错

l  类的访问权限只能是public或包访问权限(内部类除外),不能是protected和private

权限控制表:

修饰符

当前类

同一包内

子孙类

其他包

其他包子孙类

public

Y

Y

Y

Y

Y

protected

Y

Y

Y

N

Y/N(说明

default

Y

Y

N

N

N

private

Y

N

N

N

N

说明:

  • 基类的 protected 成员是包内可见的,并且对子类可见;
  • 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。

复用类

复用类的两种方式:组合和继承

向上转型:从一个专用类型向较通用类型的转换

Final关键字:

Final数据:对于基本类型,final使数值恒定不变;对于对象引用,final使引用恒定不变(一旦final引用被初始化指向一个对象,就无法再把他改为指向另一个对象)

Final方法:定义final方法以防止任何继承类修改他的含义,即防止子类覆盖父类的final方法

Final类:当不希望一个类被继承时,可以将一个类定义为final类

多态

多态是同一个行为具有多种不同表现形式的能力,实现多态必须满足三点:继承、方法重写以及父类引用指向子类对象

静态方法是与类,而不是与单个的对象相关联,所以如果一个方法是静态方法,则其无法具有多态性

接口

抽象类:包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,则该类必须定义为抽象类。抽象方法是指仅有方法声明而没有方法体的方法。

接口是Java实现多重继承的途经

Java容器

基本的集合类:List,Set,Queue和Map,java容器类都可以自动地调整尺寸

List必须按照插入的顺序保存元素

Set对于每个值都只保存一次(不能有重复元素)

Map是允许将某些对象与其他一些对象关联起来的关联数组

Collection的构造器可以接收另一个Collection,用来初始化自身。首选使用Collection.addAll()的方式。也可以使用Arrays.asList()的输出作为一个List,但是使用此种方法,其底层是数组,所以不能调整尺寸,也就不能再调用add()方法和delete()方法。示例:

      List<Integer> list1 = new ArrayList<Integer>();

      Collections.addAll(list1, 1, 2, 3);

      for (Integer integer : list1) {

          System.out.println(integer);

      }

     

      List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

ArrayList和LinkedList比较:

l  ArrayList在随机访问元素上面有优势,但是在中间插入和移除元素时较慢

l  LinkedList在随机访问时较慢,但是能够通过较低的代价在其中插入和移除元素

LinkedList具有实现Stack功能的所有方法;另外LinkedList实现了Queue接口,因此LinkedList可以用作Queue的一种实现,并可以向上转型为Queue

l  TreeSet将元素存储在红黑树数据结构中(改进的二叉查找树),保持元素排序状态

l  HashSet使用的是散列函数,HashSet提供最快的查询速度

l  LinkedHashList也使用了散列函数,不过看起来是使用了链表来维护元素插入顺序


猜你喜欢

转载自blog.csdn.net/weixin_42534940/article/details/81016088