内存中的最小区域,保存了下一条要执行的指令地址在哪。程序想要运行,JVM就得把字节码加载器来访到内存中,程序就会一条一条把指令从内存中取出来放到CPU上执行,因此需要记住当前执行到哪一条。操作系统是以线程为单位进行调度执行的,每个线程都得记录自己的执行位置,因此程序计数器每个线程都会有一个。
存放局部变量和方法调用信息,方法调用的时候每次调用一个新的方法就都涉及到“入栈”操作、每次执行完了一个方法都涉及到“出站”操作。每个线程都有一个栈。
一个进程只有一份,多个线程共用一个堆,因此也是内存中空间最大的区域。new出来的对象存放在堆中,对象的成员变量自然也是在堆中了。因此可以认为局部变量在栈上,成员变量和new的对象在堆上。
方法区中存放的是“类对象”(.java->.class二进制字节码文件),.class会被加载到内存中,也就被JVM构造成了类对象(加载的过程就称为“类加载”)。这里的类对象就是放到了方法区,类对象描述了这个类长什么样(泪的名字是啥,有啥成员,有哪些方法,每个成员叫啥名字是啥类型,每个方法叫啥名字是啥类型),除此之外静态成员成为了“类属性”,普通的成员是“实例属性”。
把.class文件加载到内存中构建成类对象

先找到对应的.class文件,然后打开并读取.class文件,同时初步生成一个类对象。
建立实体之间的联系
a.Verification
验证读到的内容是不是和规范中的格式完全匹配,如果发现这里读到的数据格式不符合规范,就会类加载失败并且抛出异常。
b.Preparation
给静态变量分配内存,并设置初始值。
c.Resolution
解析阶段是java虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常亮的过程。.class文件中常量是集中放置的,没一个常量都有一个编号,.class稳健的结构体初始化情况只是记录了编号,就需要根据编号找到对应的内容填充到类对象中。
真正地对类对象初始化,尤其针对静态成员。
经典面试题:



加载:程序是从main方法开始执行的,main是Test的方法,因此要执行main就需要先加载Test。Test继承自B要加载Test就要先加载B,B继承自A就要先加载A。因此先有前两行AB的静态代码块。
构造:要想构造Test,就得先构造B,要想构造B,就得先构造A。对于A来说构造过程(构造代码块->构造方法的执行)
因此大的原则:
JVM中的类加载器如何根据类的全限定名(java.lang.String)找到.class文件的过程。
JVM里提供了专门的对象叫做“类加载器”负责进行类加载,当然找文件的过程也是类加载器来负责的,.class文件放置的位置可能有很多,因此JVM里面提供了多个类加载器,每个类加载器负责一个分区,默认的类加载器主要是3个:
程序员还可以自定义加载器,来加载目录中的类,Tomcat就自定义了类加载器,用来专门加载webapps里面的.class。
双亲委派模型就描述了这个找目录过程。

程序启动先进入ACL类加载器

程序启动先进入ACL类加载器
设计原因:一旦程序员自己写的类和标准库中的类,全限定类名重复了也能够顺利加载到标准库中的类。自定义的类加载器可以遵守,也可以不遵守双亲委派,tomcat加载webapp得类就不遵守。
垃圾回收是有运行时环境通过复杂的策略,判定内存是否可以回收并进行回收的动作。垃圾回收本质上是靠运行时环境额外做了很多工作,来完成自动释放内存的操作。
劣势:
1.消耗额外开销
2.可能会影响程序的流畅运行(垃圾回收经常会引入STW问题)
垃圾回收的内容?内存
内存又分为以下几种:

途中需要进行回收的是不再使用单时尚未回收的内存。对于一部分仍然使用,一部分不在的对象,整体来说是不是放的,等到这个对象彻底完全不使用才真正释放。
a.基于引用计数(Python采取的方案)

针对每个对象都会额外引入一小块内存,保存这个对象有多少个引用指向它,这个内存不再使用就释放了(引用计数为0)
缺点:
例子:
class Test{
Test t =null;
}
Test t1 = new Test();
Test t2 = new Test();
t1.t = t2;
t2.t = t1;
t1 = null;
t2 = null;

此时t1,t2,以及两个对象的状态如上图所示,两个对象的引用计数不为0,所以无法释放。但是由于引用长在彼此身上,外界的代码无法访问到这两个对象。此时此刻这两个对象就被孤立了,既不能使用又不能释放,这就出现了内存泄露的问题。
b.基于可达性分析(java采取的方案)
通过额外的线程,定期的针对整个内存空间的对象进行扫描,由一些起始位置(称为GCRoots),会类似于深度优先遍历一样,把可以访问到的对象都标记一遍(带有标记的对象就是可达的对象),没被标记的对象就是不可达对象(垃圾)。
GCRoots:
优点:空间利用率低,解决了循环引用的问题
缺点:系统开销大,遍历一次可能比较慢
a.标记清除

标记就是可达性分析的过程,清楚就是直接释放内存。此时如果直接释放虽然内存是还给系统了,但是释放的内存是离散的会带来“内存碎片”问题。
b.复制算法

复制算法是为了解决内存碎片的问题,用一半丢一半直接把不是垃圾的拷贝到另一半,把原来整个空间整体都释放掉。此时内存碎片的问题就迎刃而解了。
缺点:内存空间利用率低,如果要保留的对象多,释放的对象少,复制开销就很大。
c.标记整理
针对复制算法做出的改进

原理类似于顺序表删除中间元素,有一个搬运操作。这个方案空间利用率是高了,但是仍然没有解决复制/搬运元素开销大的问题
d.综合使用(JVM)
实际的JVM中的实现会把多种方案结合起来使用,即“分代回收”,针对对象进行分类。根据对象“年龄”分类,一个对象熬过一轮GC的扫描就称为“长了一岁”,针对不同年龄采取不同方案。

比较老的垃圾收集器
新的收集器
1)CMS收集器
2)G1收集器(唯一一款全区域的垃圾回收器,Java11de JVM)
把整个内存分成了很小的区域Region,给这些Region进行了不同的标记,有的Region方新生代对象,有的对象放老年代对象。然后在扫描的时候一次扫若干Region(不追求一轮GC就扫描完分多次扫),相比CMS对于业务代码影响更小。G1在当下可以优化到让STW停顿时间小于1ms。