程序计数器是当前线程所执行的字节码的行号指示器,每条线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。
每个方法在执行的过程中都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出站的过程
本地方法栈与虚拟机栈类似,只不过后者为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。
唯一的目的是存储对象的实例
与堆一样,是各个线程共享的内存区域,他用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。编译器生成的各种字面量和符号引用存储在类加载后进入方法区的运行时常量池中。
直接内存并不是JVM管理的内存,可以这样理解,直接内存就是JVM以为的机器内存。比如机器有4G的内存, 占用了1G, 则其余3G就是直接内存。JDK有一种基于Channel和Buffer的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用。
引用计数法难以处理循环引用的对象的垃圾回收问题,java C#等语言使用可达性分析来判断对象是否存活。基本思想是通过一系列的成为GC roots的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,有如果从GC root到这个对象不可达,则证明此对象是不可用的。GC Root对象包含以下几种
- 虚拟机栈中的引用对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象
- 标记-清除算法 ----GC算法的基础
- 停止-复制算法 Eden Survivor 8:1:1 ----年轻代回收算法
- 标记整理算法 ----老年代回收算法
CMS收集器
用于老年代回收,致力于获取最短的回收停顿时间,使用标记清除算法,多线程,优点是并发收集,停顿小
G1收集器
- G1在压缩空间方面有优势;
- 通过将内存空间分成区域的方式避免内存碎片;
- Eden,Survivor Old区不在固定,在内存使用效率上来说更灵活;
- G1会在Yong GC中使用、而CMS只能在O区使用;
- 对象优先分配在Eden中
- 大对象直接进入老年代
- 长期存活的对象将进入老年代(经历一次GC仍存活则年龄加一)
加载
- 通过一个类的完全限定名蔡获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.long.Class对象,作为方法区这个类的各种数据的访问入口
验证
- 文件格式的验证, 如是否以魔数开头,常量池中的常量是否有不被支持的类型等
- 元数据验证,如这个类是否有父类,是否实现了父类或接口中要求实现的所有方法等
- 字节码验证,比如抱枕跳转指令不会跳转到方法体以外的字节码指令上。保证方法体中的类型转换是有效的
- 符号引用验证,如符号引用的类,字段,方法的访问性是否可以被当前类访问
准备
本阶段为类变量分配内存并设置类变量的初始值,注意这里指的是类变量
解析
解析阶段是讲虚拟机常量池内的符号引用替换为直接引用的过程,所谓符号引用即以一组符号描述所引用的目标,如一个方法的名称,类名等。解析的过程是将这些符号解析为直接指向目标的指针,相对偏移量或者一个能间接定位到目标的句柄。
- 类或者接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
初始化
clinit
由编译器自动收集类中所有变量的赋值动作和静态语句块中的语句合并产生。
虚拟机保证子类的clinit
方法执行前会先执行父类的clinit
方法,因此虚拟机中第一个被执行的clinit
方法的类一定是Object
其要求除了顶层的启动类加载器之外,其余的类加载器都应该有自己的父类加载器,这里的父类加载器之间的父子关系一般不会以集成的关系来实现,而都是使用组合关系来复用父加载器。其工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,自加载器才会尝试去加载。
ArrayList基于数组,LinkedList基于链表
HashMap内部实现就是一个哈希表,即一个数组,数组的元素是一个链表的头结点。 当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高。HashMap在创建时有一个负载因子,当元素的个数大于数组长度×负载因子的大小时,HashMap就会进行扩容。把数组的大小扩展2倍。然后重新计算各个元素的hash值。
Fail-Fast
HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了Map,那么将抛出ConcurrentModificationException,这就是Fail-Fast策略 其实现是通过modCount域,modeCount记录修改次数,迭代器在初始化的过程中会将这个值保存在expectedModCount,在迭代的过程中,判断modCount与expectedModCount是否相等,如果不相等,说明已经有其他线程修改了Map,注意这里的modCount必须声明为volatile,以保证线程之间的可见性。
TreeMap的内部通过红黑树实现。根据键的自然顺序进行排序,或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法。 TreeMap的基本操作containsKey,get,put和remove的时间复杂度是logn
- HashTable基于Dictionary类,而HashMap基于AbstractMap
- HashMap的key和value都允许为null,而HashTable的key和Value都不允许为null
- HashTblae中急速所有的public方法都是synchronized,而有些方法也是在内部通过synchronized代码块来实现。
- 传统的IO是基于字节的,所有的IO都被视为单个字节的移动(流);而NIO是基于块的,其性能优于流IO。
- NIO中有两个概念Buffer和Channel。Buffer本质上就是一块内存区,可以用来写入数据,并在稍后读取出来。通道可以读也可以写,通道可以异步读写,且通道总是基于Buffer来读写。通道与流非常相似。
- NIO基于IO复用模型,即selector模型或epoll模型,使得单个线程可以监听多个通道(IO)描述符,监听到符合条件的描述符后会进行数据的读写或链接建立等操作。
正常情况下new出来的对象的引用就是强引用,如果一个对象具有强引用,那么垃圾回收绝不会回收它。当内存空间不足,jvm宁愿抛出OutOfMemoryError错误使程序异常终止
如果一个对象只具有软引用,如果内存空间不足,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可以用来实现内存敏感的告诉缓存。
弱引用与软引用的区别在于,只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存
虚引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于,虚引用必须和引用队列联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有许引用,就会在回收对象的内存之前把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了徐应用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
toString() | clone() | wait() | notify() |
---|---|---|---|
notifyAll() | hashcode() | equals() | finallize() |
默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。
重写equals()需要符合以下规则:
- 自反性。对于任何非null的引用值x,x.equals(x)应返回true。
- 对称性。对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才返回true。
- 传递性。对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。
- 一致性。对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false。
对于任何非空引用值x,x.equal(null)应返回false。
重写equals()的同时需重写hashCode()
java中,可以使用hashCode()来获取对象的哈希码,其值就是对象的存储地址,这个方法在Object类中声明,因此所有的子类都含有该方法。
在Java API文档中关于hashCode方法有以下几点规定:
- 在java应用程序执行期间,如果在equals方法比较中所用的信息没有被修改,那么在同一个对象上多次调用hashCode方法时必须一致地返回相同的整数。如果多次执行同一个应用时,不要求该整数必须相同。
- 如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须返回相同的整数。
- 如果两个对象通过调用equals方法是不相等的,不要求这两个对象调用hashCode方法必须返回不同的整数。但是程序员应该意识到对不同的对象产生不同的hash值可以提供哈希表的性能。
HTTP是一种无状态的协议,Cookie与Session都是常见的会话跟踪技术。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。Cookie存在客户端所以用户可以看见,所以也可以编辑伪造,不安全。Session过多的时候会消耗服务器资源,所以大型网站会有专门的Session服务器,而Cookie存在客户端所以没什么问题。域的支持范围不一样,比方说a.com的Cookie在a.com下都能用,而www.a.com的Session在api.a.com下都不能用。
Session的应用需要Session_id来实现。id是存在Cookie里的,如果禁用Cookie, Session也无法正常使用,但可以通过URL地址重写实现可用。