1.Java程序运行机制详细说明
首先利用IDE集成开发工具编写Java源代码,源文件的后缀为.java;
再利用编译器(javac命令)将源代码编译成字节码文件,字节码文件的后缀名为.class;
运行字节码的工作是由解释器(java命令)来完成的。
java文件通过编译器变成了.class文件,接下来类加载器又将这些.class文件加载到JVM中。
其实可以一句话来解释:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。
2.JVM运行时数据区
1、堆
JVM中最大的一块,主要用来存放对象实例和数组,几乎所有的对象实例都在这里分配内存。线程共享,内部会划分出多个线程私有的分配缓冲区(TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。
2、虚拟机栈
每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。线程私有,生命周期和线程一致。
3、方法区(非堆)
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
4、本地方法栈
本地方法栈(Native MethodStacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
5、程序计数器
程序计数器(Program CounterRegister)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域
3.说说堆和栈的区别
1)物理地址
堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,
所以有各种算法。比如,标记-消除,复制,标记-整理,分代(即新生代使用复制算法,老年
代使用标记——压缩)
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
2)内存分配
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大
于栈。
栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
3)存放的内容
堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
栈存放:局部变量,操作数栈,返回结果。栈区更关注的是程序方法的执行。
4)程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
4.引用类型
对象引用类型分:强引用、软引用、弱引用和虚引用。
强引用:不会被回收。就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收。
软引用:内存充足,则不回收;不足则回收。软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。
弱引用:会被回收。弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。
主流的有三种垃圾回收算法:复制算法,标记-清除算法、标记-整理算法
5.JVM有多种垃圾回收算法,其中目前在用最经典的就是分代收集算法。
永久代(Perm):主要保存class,method,field等对象,该空间大小,取决于系统启动加载类的数量,一般该区域内存溢出均是启动时溢出。java.lang.OutOfMemoryError: PermGen space
老年代(Old):一般是经过多次垃圾回收(GC)没有被回收掉的对象。
伊甸园(Eden):新创建的对象。
幸存区0(Survivor0):经过垃圾回收(GC)后,没有被回收掉的对象。
幸存区1(Survivor1):同Survivor0相同,大小空间也相同,同一时刻Survivor0和Survivor1只有一个在用,一个为空。
6.标记-清除算法
首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。缺点:标记和清除两个过程效率都不高;标记清除后会产生空间碎片,空间碎片导致分配较大对象时可能提前触发垃圾回收。
7.复制算法
将可用内存分为两个区域,每次只使用其中一块,当使用的那一块内存用完时,将还存活的对象复制到另外一块内存中,然后把已使用过的内存空间一次清理掉。优点:解决的空间碎片问题,实现简单。缺点:需要两倍空间,将内存缩小为两块,内存使用率不高。复制操作频繁效率变低。
8.标记-整理算法
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
9.垃圾收集器CMS G1
垃圾回收策略可以看作是内存回收的抽象策略,而垃圾收集器是内存回收的具体实现
垃圾收集器有很多种,常见的有:串行收集器、并行收集器、并发收集器、CMS收集器以及最新的G1收集器。重点为CMS收集器和G1收集器
10.CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器
基于 标记清除 算法实现。第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
特点:
针对老年代
基于"标记-清除"算法(不进行压缩操作,会产生内存碎片)
以获取最短回收停顿时间为目标
并发收集、低停顿
需要更多的内存
运作步骤:
初始标记: 暂停所有的其他线程,标记GC Roots能直接关联到的对象,速度很快;
并发标记:进行GC Roots Tracing的过程;
重新标记: 修正并发标记期间的变动部分,需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫,回收所有的垃圾对象。
缺点:
对 CPU 资源敏感;
无法收集浮动垃圾;
标记清除 算法带来的空间碎片。
11.G1收集器
G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器。以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
G1是将整个堆空间分成许多个大小不等的独立区域(Region),大约有2000块,每个Region从1M到32M大小不等,在JVM启动的时候就已经分割好了,Region可采用并行的垃圾回收或 NOT STW 方式。
运作步骤:
初始标记(Initial Marking)
并发标记(Concurrent Marking)
最终标记(Final Marking)
筛选回收(Live Data Counting and Evacuation)
特点:
1、并行与并发
G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
2、分代收集
虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
能够采用不同方式处理不同时期的对象;
虽然保留分代概念,但Java堆的内存布局有很大差别;
将整个堆划分为多个大小相等的独立区域(Region);
新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合。
3、空间整合
与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。是一种类似火车算法的实现,不会产生内存碎片,有利于长时间运行。
4、可预测停顿
这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型。可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。在低停顿的同时实现高吞吐量。
为什么G1可以实现可预测停顿?
可以有计划地避免在Java堆的进行全区域的垃圾收集;
G1收集器将内存分大小相等的独立区域(Region),新生代和老年代概念保留,但是已经不再物理隔离。
G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表;
每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);
12.类加载器,加载类的流程
类加载器实现的功能是:为加载阶段获取二进制字节流
1、加载
加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。
在加载阶段,JVM需要完成3件事:
通过类的全限定名获取该类的二进制字节流;
将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2、连接
2.1 验证
验证阶段,确保加载进来的字节流符合JVM规范。将完成以下4个阶段的检验动作:
文件格式验证
元数据验证(是否符合Java语言规范)
字节码验证(确定程序语义合法,符合逻辑)
符号引用验证(确保下一步的解析能正常执行)
2.2 准备
准备阶段,为静态变量在方法区分配内存,并设置默认初始值。
2.3 解析
解析阶段,是虚拟机将常量池内的符号引用替换为直接引用的过程。
3、初始化
初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。
(注:当有父类且父类为初始化的时候,先去初始化父类;再进行子类初始化语句)
类加载器之间的这种层次关系叫做双亲委派模型。
13.双亲委派模型
双亲委派模型要求除了顶层的启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不是以继承关系实现的,而是用组合实现的。
14.双亲委派模型的工作过程
如果一个类接受到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器(Bootstrap ClassLoader)。
只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。
15.为何要双亲委派机制(避免同一个类加载出不同的结果使得程序混乱)
为何要采用双亲委派机制呢?了解为何之前,我们先来说明一个知识点:判断两个类相同的前提是这两个类都是同一个加载器进行加载的,如果使用不同的类加载器进行加载同一个类,也会有不同的结果。
如果没有双亲委派机制,会出现什么样的结果呢?比如我们在rt.jar中随便找一个类,如java.util.HashMap,那么我们同样也可以写一个一样的类,也叫java.util.HashMap存放在我们自己的路径下(ClassPath).那样这两个相同的类采用的是不同的类加载器,系统中就会出现两个不同的HashMap类,这样引用程序就会出现一片混乱。
标签:java,标记,引用,收集器,回收,基础知识,内存,加载 From: https://blog.51cto.com/u_16243779/7417341