(1)、英文和数字占一个字节
(2)、中文占一个字符,也就是两个字节
(3)、字符 不等于 字节。
字符(char)是 Java 中的一种基本数据类型,由 2 个字节组成,范围从 0 开始,到 2^16-1。
字节是一种数据量的单位,一个字节等于 8 位。所有的数据所占空间都可以用字节数来衡量。例如一个字符占 2 个字节,一个 int 占 4 个字节,一个 double 占 8 个字节 等等。
(4)、字节(Byte)=8位(bit) [6]
1KB( Kilobyte,千字节)=1024B [6]
1MB( Megabyte,兆字节)=1024KB [6]
1GB( Gigabyte,吉字节,千兆)=1024MB [6]
1TB( Trillionbyte,万亿字节,太字节)=1024GB [6]
1PB( Petabyte,千万亿字节,拍字节)=1024TB [6]
1EB( Exabyte,百亿亿字节,艾字节)=1024PB [6]
1 ZB(Zettabyte,十万亿亿字节,泽字节)=1024EB [6]
1YB( Yottabyte,一亿亿亿字节,尧字节)=1024ZB [6]
BB( Brontobyte,千亿亿亿字节)=1024YB [6]
【简单例子】
public class MyClass {
public static void main(String[] args) {
String s1 = new String("Hello World!");
String s2 = new String("Hello World!");
String s3 = "Hello World!";
String s4 = "Hello World!";
int t1 = new Integer(1);
int t2 = new Integer(1);
int t3 = 1;
int t4 = 1;
System.out.println("两个new String对象:" + (s1 == s2));
System.out.println("两个String对象:" + (s3 == s4));
System.out.println("两个new Interger对象:" + (t1 == t2));
System.out.println("两个int对象:" + (t3 == t4));
}
}
输出
两个new String对象:false
两个String对象:true
两个new Interger对象:true
两个int对象:true
基本数据类型(也称原始数据类型):byte,short,char,int,long,float,double,boolean。他们之间的比较,应用双等号(),比较的是他们的值。
复合数据类型(类):当他们用()进行比较的时候,比较的是他们在内存中的存放地址(确切的说,是堆内存地址)。
注:对于第二种类型,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。因为每new一次,都会重新开辟堆内存空间。
一 简单介绍
1.1 程序计数器
每个线程拥有一个PC寄存器
在线程创建时创建
指向下一条指令的地址
执行本地方法时,PC的值为undefined
1.2 方法区
保存装载的类信息
类型的常量池
字段,方法信息
方法字节码
通常和永久区(Perm)关联在一起
1.3 堆内存
和程序开发密切相关
应用系统对象都保存在Java堆中
所有线程共享Java堆
对分代GC来说,堆也是分代的
GC管理的主要区域
1.4 Java虚拟机栈
核心:一个线程一个栈,一个方法一个栈帧
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于储存局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
栈内存就是虚拟机栈,或者说是虚拟机栈中局部变量表的部分。局部变量表存放了编辑期可知的各种基本数据类型(boolean、byte、char、short、int、long、double、float)、对象引用(reference)类型和returnAddress类型(指向了一条字节码指令的地址)
其中64位长度的long和double类型的数据会占用两个局部变量空间,其余的数据类型只占用1个。
Java虚拟机规范对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果虚拟机扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
1.5 本地方法栈
本地方法栈和虚拟机栈发挥的作用是非常类似的,他们的区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
1.6 运行时常量池
它是方法区的一部分。class文件中除了有关的版本、字段、方法、接口等描述信息外、还有一项信息是常量池,用于存放编辑期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
Java语言并不要求常量一定只有编辑期才能产生,也就是可能将新的常量放入池中,这种特性被开发人员利用得比较多是便是String类的intern()方法。
当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
二.详细介绍
1、程序计数器:线程私有,一块较小内存空间,当前线程执行字节码的行号指示器,线程运行、切换、恢复时记录执行的字节码指令地址位置。
2、java虚拟机栈:线程私有,生命周期和线程相同。描述的是java方法执行的内存模型
每个方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。从调用到执行完成的过程对应一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表存放了编译器可知的8种基本数据类型、对象引用和returnAddress类型(指向了一条字节码指令的地址),所需内存大小编译期确定。最小存储单位是slot,第一个存放的是this对象引用,其后是方法参数变量、方法局部变量。
该区域规定了2种异常: StackOverflowError 请求栈深度大于虚拟机允许的深度 OutOfMemeryError
虚拟机栈动态扩展时无法申请到足够内存。
3、本地方法栈:和虚拟机栈类似,为Native方法服务,HotSpot虚拟机已将该栈和虚拟机栈合并。
4、java堆
线程共享,虚拟机启动时创建,唯一目的就是存放对象实例,存放几乎所有对象实例以及数组,GC堆。
线程私有缓冲区内存从Java堆中划分
物理上是不连续的内存空间,逻辑上连续
5、方法区
线程共享,存储已被虚拟机加载的类信息(版本、字段、方法、接口)、常量、静态变量、即时编译器编译后的代码等数据(多个类实例共享数据,如静态变量、类hashCode、指向锁记录的指针,线程ID、对象分代年龄)
HotSpot将方法区作为Java堆的一部分,当做永久代(Permanent Generation)
HotSpot JDK1.7已将永久代的字符串常量池(hashMap表)移到堆内存中。
6、运行时常量池
方法区的一部分,存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中。
字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和
描述符。
7、直接内存:Native函数库直接分配堆外内存
8、HotSpot对象的创建
(1)虚拟机遇到new指令时,首先检查该指令参数在常量池中能否定位到一个类符号的引用。并且检查这个符号代表的类是否已被加载、解析和初始化过
对象所需要的内存在类加载检查后大小已完全确定。
a、内存规整使用“指针碰撞”分配内存,即将指针像后方空闲内存移动分配内存大小的偏移量,Serial、ParNew等带有整理内存功能的收集器使用该方法分配。
b、堆内存不规整使用“空闲列表”分配内存,维护一个列表记录哪些内存是可用的,分配内存时在列表中找到一个内存足够大的空间划分给对象实例
(2)多线程情况则非线程安全
a、使用cas+失败重试的方式保证更新操作的原子性
b、按照线程划分在不同的空间之中进行,分配内存时先在本地线程分配缓冲(TLAB),只有TLAB用完并分配新的TLAB时,才需要同步锁定。
(3)对象内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),接下来要对对象进行必要的设置,如对象是哪个类的实例,如何才能找到类的元数据信息、对象的hash码、GC分代年龄等信息。
8、对象的内存布局:对象头、实例数据、对齐填充 3大块
对象头(存储在方法区):一部分存储对象自身运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分是类指针,指向类元数据的指针,确定这个对象是哪个类的实例。java数组对象还必须有一块记录数组长度的数据。
内存溢出和内存泄漏的区别
https://www.cnblogs.com/william-dai/p/10901653.html
2.1 GC 回收原理
参考地址 https://blog.csdn.net/lhl1124281072/article/details/80524837
现在的GC基本都采用分代收集算法,如果是分代的,那么堆也是分代的。如果堆是分代的,那堆空间应该是下面这个样子:
从GC的角度 分为新生代,老年代。
新生代:当一个对象被创建的时候,特别大的对象放在老年代,普通对象分配在年轻代,大部分对象创建以后都不再使用,对象很快变得不可达,就是对象无用,由于垃圾是被年轻代清理掉的,所以被叫做Minor GC或者Young GC。主要分为Eden区 、ServivorFrom、ServivorTo 三个区
过程:不大的新生对象new出来,放在Eden区,第一次GC后所有幸存对象放在survivor 区1,然后又有对象在Eden区new出来,第二次GC后,Eden和survivor区1中的幸存对象全都复制到survivor区2中,也就是survivor from和survivor to。经过多次GC仍然没有被回收的幸存对象转入老年代。
老年代:对象如果在创建时就非常大,或者在新生代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行Major GC,也叫 Full GC。
新生代
Minor GC 采用 复制算法
老年代
MajorGC 采用 标记清除算法
在进行MajorGC 前一般都进行了一次MinorGC
GC常用的回收算法:标记清除(Mark-Sweep) 复制算法(Coping) 标记整理(Mark Compact ) 分代收集(Generation Collevting)
如何确定是否回收
引用计数器 :操作对象必定会引用 计数器就会+1 当为0时这说明未被使用可回收
根搜索算法(可达性分析 GC ROOTS): 根路径向下寻找没有路 至少标记2次才会被回收
3.java对象相关
面向对象的特征 (OOP)
1.抽象:
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。比如,我们要设计一个学生成绩管理系统,考察学生这个对象时,我们只关心他的班级、学号、成绩等,而不用去关心他的身高、体重这些信息。抽象包括两个方面,一是过程抽象,二是数据抽象。过程抽象是指任何一个明确定义功能的操作都可被使用者看作单个的实体看待,尽管这个操作实际上可能由一系列更低级的操作来完成。数据抽象定义了数据类型和施加于该类型对象上的操作,并限定了对象的值只能通过使用这些操作修改和观察。
2.继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。这也体现了大自然中一般与特殊的关系。继承性很好的解决了软件的可重用性问题。比如说,所有的Windows应用程序都有一个窗口,它们可以看作都是从一个窗口类派生出来的。但是有的应用程序用于文字处理,有的应用程序用于绘图,这是由于派生出了不同的子类,各个子类添加了不同的特性。
3.封装:
封装是面向对象的特征之一,是对象和类概念的主要特性。封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。一旦定义了一个对象的特性,则有必要决定这些特性的可见性,即哪些特性对外部世界是可见的,哪些特性用于表示内部状态。在这个阶段定义对象的接口。通常,应禁止直接访问一个对象的实际表示,而应通过操作接口访问对象,这称为信息隐藏。事实上,信息隐藏是用户对封装性的认识,封装则为信息隐藏提供支持。封装保证了模块具有较好的独立性,使得程序维护修改较为容易。对应用程序的修改仅限于类的内部,因而可以将应用程序修改带来的影响减少到最低限度。
4. 多态性:
多态性是指允许不同类的对象对同一消息作出响应。比如同样的加法,把两个时间加在一起和把两个整数加在一起肯定完全不同。又比如,同样的选择编辑-粘贴操作,在字处理程序和绘图程序中有不同的效果。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
面向对象程序设计具有许多优点:
1、开发时间短,效率高,可靠性高,所开发的程序更强壮。由于面向对象编程的可重用性,可以在应用程序中大量采用成熟的类库,从而缩短了开发时间。
2、应用程序更易于维护、更新和升级。继承和封装使得应用程序的修改带来的影响更加局部化。
设计原则
分别是:单一职责原则、开放封闭原则、依赖倒置原则、接口隔离原则和Liskov替换原则、迪米特法则 不相互通信类 不要直接互相作用
单一职责原则
对于单一职责原则,其核心思想为:一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。
专注,是一个人优良的品质;同样的,单一也是一个类的优良设计。交杂不清的职责将使得代码看起来特别别扭牵一发而动全身,有失美感和必然导致丑陋的系统错误风险。
开放封闭原则
对于开放封闭原则,它是面向对象所有原则的核心,软件设计说到底追求的目标就是封装变化、降低耦合,而开放封闭原则就是这一目标的最直接体现。
开放封闭原则,其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
因此,开放封闭原则主要体现在两个方面:1、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。2、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
实现开开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。
“需求总是变化”没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响。
依赖倒置原则
对于依赖倒置原则,其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一层不变的。依赖于抽象,就是对接口编程,不要对实现编程。
接口隔离原则
对于接口隔离原则,其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。
Liskov替换原则
对于Liskov替换原则,其核心思想是:子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。
Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。
java 代理分类
https://www.cnblogs.com/leeego-123/p/10995975.html
4.java执行原理
https://www.cnblogs.com/yangshaox/p/11611696.html
jdk bin 目录主要有 javac.exe 编译器 java.exe 执行器
java程序执行过程分为两步,下图为流程示意图
第一步:将java源码(.java文件)通过编译器(javac.exe)编译成JVM文件(.class文件)
第二步:将JVM文件通过java.exe执行,输出结果
字节码转为各平台的机器码 通过编译器线程去处理
通过如上分析,我们发现JVM至关重要,其向上屏蔽了操作系统的差异,也正因为JVM的该作用,才使java这门编程语言能够实现跨平台,
其原理大致可描述为如下:
Java代码执行过程概述
Java代码经历三个阶段:源代码阶段(Source) -> 类加载阶段(ClassLoader) -> 运行时阶段(Runtime)
首先我们来理清一下Java代码整个执行过程, 让我们对其有个整体的认识:
Java源程序(.java)经过Java编译器(javac)以后, 生成一个或多个字节码(.class)文件, JVM将每一条要执行的字节码通过类加载器ClassLoader加载进内存, 再通过字节码校验器的校验, Java解释器翻译成对应的机器码, 最后在操作系统解释运行.
当程序要使用某个类时, 如果该类还未被加载到内存中, 则系统会通过加载, 连接, 初始化三步来实现对这个类进行初始化:
加载就是将class文件读入内存, 并为之创建一个Class对象(任何类被使用时系统都会创建且只创建一个Class对象)
JVM进行类加载阶段需要完成以下三件事情:
1. 通过一个类的全限定名称来获取定义此类的二进制字节流
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3. 在java堆中生成一个代表这个类的java.lang.Class对象, 作为方法区这些数据的访问入口
类的加载的最终产品是位于堆区中的Class对象, Class对象封装了类在方法区内的数据结构, 并且向Java程序员提供了访问方法区内的数据结构的接口
1. 创建类的实例
2. 使用类的静态变量或者为静态变量赋值
3. 调用类的静态方法
4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
5. 初始化某个类的子类
6. 直接使用java命令来运行某个主类
通俗的说就是只要用到了类的东西类就会加载
JVM在运行时会产生3个类加载器组成的初始化加载器层次结构
Bootstrap ClassLoader 根类加载器
用C++编写
也被称为引导类加载器, 负责java核心类的加载 该加载器无法直接获取
比如System, String等, 在JDK中JRE的lib目录下rt,jar文件中
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载 jre/lib/ext目录下的jar包或-Djava,ext,dirs指定目录下的jar包装入工作库
System ClassLoader 系统类加载器(加载自己写的类以及第三方类库(导入的jar包))
负责在JVM启动时加载来自java命令的class文件, 以及classpath环境变量所指定的jar包和类路径
连接就是将类的二进制数据合并到JRE中
连接分为以下三步:
验证 检查载入Class文件数据的正确性
文件格式检验:检验字节流是否符合Class文件格式的规范, 并且能被当前版本的虚拟机处理
元数据检验:对字节码描述的信息进行语义分析, 以保证其描述的内容符合Java语言规范的要求
字节码检验:通过数据流和控制流分析, 确定程序语义是合法、符合逻辑的
符号引用检验:符号引用检验可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验
是否有正确的内部结构(构造器, 方法, 变量, 代码块), 并和其他类协调一致
准备 该阶段正式为类变量分配内存并设置类变量初始值
这些变量所使用的内存将在方法区中进行分配, 此时进行内存分配的仅包括类变量, 而不包括实例变量(实例变量将会在对象实例化时随着对象一起分配在Java堆中),
另外, 在这里分配的静态类变量是将其值定义为默认值, 这里所设置的初始值通常情况下是数据类型默认的零值(如0, 0L, null, false等), 而不是被在Java代码中
被显式地赋予的值, 正确的赋值将在初始化阶段执行,
解析 将类的二进制数据中的符号引用替换为直接引用
比如说类中方法中的运算, 运算中符号a=1 去掉a直接变成1, 这样可以节约很多资源
初始化就是对类的静态变量, 静态代码块执行初始化操作
类初始化阶段是类加载过程的最后一步, 前面的类加载过程中, 除了加载(Loading)阶段用户应用程序可以通过自定义类加载器参与之外, 其余动作完全由虚拟机主导和控制, 到了初始化阶段, 才真正开始执行类中定义的Java程序代码
初始化为类的静态变量赋予正确的初始值, JVM负责对类进行初始化, 主要对类变量进行初始化, 在Java中对类变量进行初始值设定有两种方式:
声明静态变量(类变量)时指定初始值
使用静态代码块为类变量指定初始值
初始化步骤:
1. 假如这个类还没有被加载和连接, 则程序先加载并连接该类
2. 假如该类的直接父类还没有被初始化, 则先初始化其直接父类
3. 假如类中有初始化语句, 则系统依次执行这些初始化语句
JVM在堆内存中创建对象, 类的成员变量进入到堆内存中, 赋默认值
最后就是我们熟悉的Runtime运行时阶段
Person p = new Person();
p,study();
执行上述代码会在堆内存创建一个Person类的对象, 并且在栈内存分配一块储存空间存放Person类型的引用变量p, p存放该对象的地址并且指向该对象, 调用p的study方法实际是, 对象通过Person类的字节码对象来访问方法区Person字节码的study方法
5.程序、进程、线程关系
程序只是一组指令的有序集合,它本身没有任何运行的含义,它只是一个静态的实体。
指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体
线程是进程中的一个实体,作为系统调度和分派的基本单位。Linux下的线程看作轻量级进程。
系统要做一件事,运行一个任务,所有运行的任务通常就是一个程序;
每个运行中的程序就是一个进程,这一点在任务管理器上面可以形象的看到。
当一个程序运行时,内部可能会包含多个顺序执行流,每个顺序执行流就是一个线程。
线程和进程的区别参考
https://www.cnblogs.com/qianqiannian/p/7010909.html
Synchronized原理
被Synchronized 修饰的代码编译后会生成了 monitorenter 和 monitorexit 字节码,执行 monitorenter 会获取对象锁 没有对象锁则当前对象会获得此锁并计数器+1 否则等上一个释放。当执行 monitorexit 计数器-1 ,当计数器为0时锁就释放了。
java中锁的介绍
https://www.cnblogs.com/jyroy/p/11365935.html
java中常见的队列:
6.java 集合详解及介绍
集合主要分为三大类:Collection、Map、Iterator
【Collection 】
List
(ArrayList:有序可重复、查找速度快、增删慢、线程不安全、底层数组实现、容量不够时扩大1.5倍并+1
扩容机制 https://www.cnblogs.com/SunArmy/p/9844022.html
Vector:有序可重复、底层使用数组、速度快增删慢、线程安全效率低、默认扩容一倍
扩容机制 https://www.cnblogs.com/wuxiang/p/5328241.html
LinkedList:排列有序可重复、使用双向链表、查询增删快、线程不安全
LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据,如下:
)、
Set
(HashSet:排列无序 不可重复、底层Hash 表实现、存取速度快、内部是HashMap
TreeSet:排列无序、不可重复、底层二叉树、排序存储、
LinkedHashSet:Hash 表存储、双向链表、内部是LinkedHashMap
)、
Queue(数组、链表实现 https://www.cnblogs.com/lemon-flm/p/7877898.html)
【Map 】
HashMap:键不可重复值可以重复、底层hash表、线程不安全、键值对可为空
HashTable:键不可重复值可以重复、底层hash表、线程安全、键值对不允许为空
TreeMap:键不可重复值可以重复、底层为二叉树