首先我们来谈一下之前没有了解过的方面:

JVM:

什么是JVM?

我知道 JVM就是JavaVirtualMachine.就像是实体电脑中存在的一个小电脑一样,拥有各种计算机有的功能:包括一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个储存方法域等组成。JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(即字节码)


之前写了五个 但是XXXCSDN没有保存

1.什么是Java虚拟机?为什么Java被称作是平台无关的编程语言?

Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。他的平台无关性是因为其拥有自己一套独立于计算机操作系统的指令集以及其他解释器。

2.Java的内存结构:

JVM大体架构_加载

大致分为两块:

所有线程共享的内存区域:包括堆和方法区
单个线程私有的内存区域:包括栈,程序计数器和本地方法栈

共享的内存区域:堆:这是JVM所管理的内存的最大的一块,在JVM启动时创建,此内存区域的唯一目的就是存方对象实例,所有的对象实例都在此分配内存。方法区:它用于储存已经报备JVM加载的类信息,常量,静态变量等数据。

私有的内存区域:程序计数器:他是当前线程所执行的字节码的行号指示器。JVM栈:它描述的是Java方法执行的内存模型。每个方法被执行的时候会同时创建一个stack frame,用于储存局部变量表,操作栈,动态链接等信息。简言之,每一个方法被调用知道执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。本地方法栈:与虚拟机栈的功能类似,区别在于虚拟机栈为虚拟机执行Java方法服务 而本地方法栈则是为虚拟机使用到的Native方法服务。

3. 详细解释一下内存中的栈 堆 方法区的用法

栈空间用于保存:个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存

堆空间用于保存:通过new关键字和构造器创建的对象。所以堆是垃圾收集管理器的主要区域。堆空间可以细分为 新生代和老生代,新生代又可以分为Eden,Survivor, Tenured.

方法区用于存放已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据。

补充说明:栈空间操作起来最快但是很小,所以大的对象都是直接存放在堆空间。栈和堆的大小都可以通过JVM的启动参数的设置进行调整。栈空间用完了会出StackoverflowError,堆和常量池空间不足则会报错:OutofMemoryError.

一句话总结:

String str = new String("hello");  

上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而”hello”这个字面量是放在方法区的。

4.详细叙述一下对象在堆里的分配原则

Eden区:是一般对象优先分配在此区域内,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC(相当于年龄+1?)。

老年代区:大对象和长期存活的对象会直接进入老年代。那么如何定义长期存活?虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。但是也不会总是这么死板:动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。

总而言之 大多数对象都是沿着Eden->Survior->老年区的路径走过这一生。

5.描述一下什么是类的加载。

类的加载过程是:将类的.class文件中的二进制数据读入到内存中->将其放在运行时数据区的方法区内->在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构.

6.描述一下什么是类加载器

Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

类加载器有很多种:

JVM大体架构_句柄_02

启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库。

扩展类加载器:Extension ClassLoader,该加载器由sun.misc.LauncherKaTeX parse error: Undefined control sequence: \jre at position 25: …oader实现,它负责加载DK\̲j̲r̲e̲\lib\ext目录中,或者由…AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器。

如果用更简洁的语言去描述:

Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);

Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;

System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。

7.详细叙述一下JVM加载Class文件的原理机制?

之前说过 JVM中类的加载是由类加载器和他的子类来实现的.

由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。

那么是如何确保这四个步的呢?

类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;如果类中存在初始化语句,就依次执行这些初始化语句。

8.详细描述一下Java对象创建的过程

首先 JVM遇到一条创建对象的指令后:首先检查这个指令的参数能否在常量池中找到,然后加载这个类。第二步为对象分配内存(一种办法“指针碰撞”、一种办法“空闲列表”,最终常用的办法“本地线程缓冲分配(TLAB)”)第三步:将除对象头外的对象内存空间初始化为0 第四步:将对象的头进行必要的设置。

9.详细叙述一下类的生命周期:

类的生命周期大致分为五部分加载、连接、初始化、使用和卸载,其中前三部是类的加载的过程,如下图;

JVM大体架构_句柄_03

加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象连接,

连接,包含三块内容:验证、准备、初始化。 1)验证,文件格式、元数据、字节码、符号引用验证; 2)准备,为类的静态变量分配内存,并将其初始化为默认值; 3)解析,把类中的符号引用转换为直接引用

初始化,为类的静态变量赋予正确的初始值

使用,new出对象程序中使用

卸载,执行垃圾回收

10. 说到java的对象,能不能详述一下Java的对象结构的组成?????

包括三部分:对象头 实例数据 对齐填充

对象头两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。

实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)

对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)

11. Java对象的定位方式?

句柄池和直接指针(什么是句柄池和直接指针?)

首先来说我们为什么需要定位?

Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于在Java虚拟机规范里面只规定了reference类型是一个指向对象的引用,并没有定义这个引用应该通过什么种方式去定位、访问到堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。主流的访问方式有使用句柄和直接指针两种。

使用句柄:

使用句柄访问的话,Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据的具体各自的地址信息。如下图所示:

JVM大体架构_数据_04

使用直接指针:

使用直接指针访问的话,Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如下图所示:

JVM大体架构_数据_05

那么这两种对象访问都有什么优缺点呢?

使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。

使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访问的在Java中非常频繁,因此这类开销积小成多也是一项非常可观的执行成本。

12.我们都知道Java中的对象过一段时候会进行垃圾回收,那么我们怎么样判断对象是否到回收的时间了呢?

两种方式:

一种是我们之前提到的引用计数。每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。

第二种是:可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。(就是说当前这个时刻没有被任何对象引用过)。

13.能不能进一步谈一谈引用都有哪些分类呢?

强引用 就是指在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引 用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软引用:SoftReference 当我们堆内存占满时候就会回收这里面的对象(用来做缓存)

弱引用 也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的 对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够, 都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引 用。

虚引用 也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引 用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一 个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

Object o = new Object();

o=null; 提醒我们的gc来回收这个对象 这个引用就是虚引用

题外话:什么是GC?Garage Collector?

内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。 垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。**垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。**在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于android系统中垃圾回收的不可预知性。

那么垃圾回收机制有哪些种呢?

垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。标准的Java进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建的对象。Java平台对堆内存回收和再利用的基本算法被称为标记和清除,但是Java对其进行了改进,采用“分代式垃圾收集”。这种方法会跟Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域:

Eden. Survior, Tenured. 就像是一个对象的一生一样,但是比较少有人能完整地走过所有的进程。

关于Tenured的进一步说明:年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。

14.能不能详细的讲一下方法区的相关回收?之前一直在讲堆和栈的。

方法区回收价值很低,主要回收废弃的常量和无用的类。如何判断无用的类:1.该类所有实例都被回收(Java堆中没有该类的对象)2.加载该类的ClassLoader已经被回收3.该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方利用反射访问该类

15.详细讲一下垃圾收集算法

GC最基础的算法有四种: 标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法

标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

16.垃圾收集器有哪些类型

JVM大体架构_数据_06

17. Minor GC与Full GC分别在什么时候发生?

新生代内存不够用时候发生MGC也叫YGC,JVM内存不够的时候发生FGC

18.能简单说一下性能调优吗?