JVM
1.jvm分区:jvm大致可以分为五个取余。分别是jvm栈,本地方法栈,程序计数器,这三个是线程私有的,还有堆和方法区,这两个是共享的。
jvm栈的生命周期与线程相同,可以说是与线程同生共死,线程调用方法的时候,就会在jvm栈中压入一个对应方法的栈帧。(每个栈帧中都有局部变量表(存放局部变量),返回值、动态连接(方法调用的过程中可能会调用其他方法,而动态连接就是在调用其他方法时将符号引用转换成直接引用),操作数栈(用于存放在方法在执行过程中产生的中间结果,包括方法调用过程中的局部变量等),等等数据)
本地方法栈中存放的是本地方法,就是native方法
native方法就是指,java调用其他的非java语言实现的方法的接口
程序计数器:程序计数器指向了编译后的java字节码文件下一条执行的代码,程序计数器可以实现循环,跳转代码行等功能
堆:堆的唯一目的就是存放java实例对象。基本上所有的对象都是在堆上分配,但是也不尽然,不一定所有对象都会在堆上分配,还有可能会在栈上进行对象分配,可能发生了方法逃逸,
方法逃逸:比如说一个方法中创建了对象,但是这个对象不需要进行返回,那么这个对象就可以在栈上进行分配,因为该对象会随着方法栈帧的出栈而销毁,即使在堆上创建了对象,后续还是会被gc清理。而当方法中存在了需要进行返回的对象实例,那么这个对象就无法在栈上进行分配。方法逃逸大概就是这样了。
堆中在jdk7版本之前,大概是分成了三个部分,新生代,老年代,以及永久代,也就是我们所说的方法区吧。jdk8版本之后,永久代就被元空间取代了,就是metaspace。
为什么要替换永久代
永久代本生被设定了固定的大小上线,无法进行调整,元空间使用的是本地内存,收本机内存限制,而且可以调整元空间中的最大内存空间大小
元空间中存放的是类元数据,加载类的多少被大大增加
永久代回味gc带来不必要的复杂度,会降低回收效率
同时在堆中很容易会出现oom错误(outofmemoryError)
同时这种错误还会有其他的表现形式:比如
GC Overhead Limit Exceeded
:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
Java heap space
:假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。
方法区其实是一个逻辑上的概念,当我们需要使用一个类的时候,方法区需要读取class文件并获取相关的信息,然后将信息存放到方法区中,方法区中会存储一杯虚拟机加载的类信息,字段信息、方法信息、常量、静态变量。
class文件
class文件中有类的版本,字段,方法和接口等描述信息,同时还存放着编译时的字面量(字面量大概就是类中各种成员变量的初始值)和符号引用(符号引用包括类符号引用,字段符号引用和方法符号引用)的常量池表。常量池表会在类加载后存放到方法区的运行时常量池中。常量池表中存放的就是字面量和符号引用
字符串常量池是jvm为了提升性能和减少内存消耗针对字符串(String)专门开发出来的。主要是为了避免字符串的重复创建
jdk1.7之后 静态变量和字符串常量池都被放到了堆中。
对象的创建过程
1.类加载检查:检查类是否已经加载到方法区中,如果已经加载了,方法区中的运行时常量池中是否有改类的符号引用,并检查该符号引用是否已经被加载、解析初始化过,如果完成过,就进行对象的创建,如果没有加载,就需要先进行类加载。
2.分配内存:为新创建的对象分配内存空间,这里主要是有两个分配方法,一个事指针碰撞,适用于内存规整的情况下,还有一个就是空闲链表法,适用于内存不规整的时候,使用空闲链表法时,虚拟机会会维护一个列表,列表中会记录那些内存块可用,在内存分配时会给对象分配一块足够大的空间。然后更新列表。
同时内存分配中会存在内存分配的并发问题,因为虚拟机需要保证对象创建的过程是线程安全的。所以通常有两种情况来保证线程安全,一种是
CAS+失败重试:CAS是乐观锁的一种实现方式,也就是说,它会假设线程安全然后不加锁的去完成分配内存的这个操作,如果因为冲突而失败就会重试,直到最后成功为止。CAS+失败重试可以保证更新操作的原子性。
TLAB:在线程创建的时候,Eden区会为其分配一块TLAB空间,然后线程会先在该块区域中创建对象,如果TLAB满了或者需要创建的对象所需空间大于TLAB剩余的空间大小,那之后才会考虑CAS+失败重试的方式
同时TLAB的出现也可以说是打破了堆一定是线程共享的,TLAB使得堆在内存分配上并不是线程共享的。
3.初始化零值:在为对象分配完内存空间后,会对其分配到的空间都初始化0;这样可以保证对象的实例字段在java代码中可以不赋初始值就可以直接使用,程序可以访问到其的默认0值;
4.设值对象头:对象头中包含了该对象是那个类的实例,指向类元数据信息,对象的哈希码,GC年代分布等信息
对象包括:对象头;实例填充;对其填充三部分(对象大小必须是8字节的整数倍)
对象头包括三部分:1.标记字段:对象的哈希码,GC年代分布等
2.类指针:指向类元数据信息
3.数据长度;仅针对数组对象
5.执行init方法:<init>方法将对电工按照程序员的意愿进行初始化大概就是对数据进行赋值.
实际上就是先父后子进行类加载
双亲委派:一个类收到加载的请求,他不会自己先加载,而是会请求其父类进行加载,如果其父类还有父类,那么就会层层往上,直到顶层,然后进行类的加载。如果其父类无法进行加载,他才会自己进行加载
对象访问两种方式:句柄池;直接指针
句柄:堆中会专门分配一块内存来存储句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息。
直接指针:reference 中存储的直接就是对象的地址
类加载机制:
1.编译源代码:将Java文件编译成字节码文件
2.加载字节码文件
3.链接:检查类的字节码文件,验证字节码文件的准确性,为静态成员分配内存空间赋予初始值,解析符号引用,将符号引用转化为直接引用
4.初始化:静态变量赋值,静态代码块执行
5.定位main
6.执行main