Java 字符串一旦被创建出来就会一直存在于字符串常量池中?直到Jvm停止运行?

史上最全Java初中级面试题,发现网上很多Java初级面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~

本人发现网上虽然有不少Java相关的面试题,但第一未必全,第二未必有答案,第三虽然有答案,但未必能在面试中说,所以在本文里,会不断收集各种面试题,并站在面试官的立场上,给出我自己的答案。

如果不背 Java面试题的答案,肯定面试会挂!

这套Java面试题大全,希望对大家有帮助哈~

**1、**每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。

**2、**调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源。

**3、**接使用new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。

Java 中,可以使用 SimpleDateFormat 类或者 joda-time 库来格式日期。DateFormat 类允许你使用多种流行的格式来格式化日期。参见中的示例代码,代码中演示了将日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。

任何一种可以运行Java字节码的软件均可看成是Java的虚拟机(JVM)

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在 Java1.5 之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5 介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。

这个问题与前面的类似,适配器模式和代理模式的区别在于他们的意图不同。由于适配器模式和代理模式都是封装真正执行动作的类,因此结构是一致的,但是适配器模式用于接口之间的转换,而代理模式则是增加一个额外的中间层,以便支持分配、控制或智能访问。

优点:指定最大停顿时间、分Region的内存布局、按收益动态确定回收集

**1、**G1开创的基于Region的堆内存布局是它能够实现这个目标的关键。虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

**2、**虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获取尽可能高的收集效率。

**3、**G1收集器的运作过程大致可划分为以下四个步骤:·初始标记 (Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。·并发标记 (Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。·最终标记 (Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。·筛选回收 (Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。从上述阶段的描述可以看出,G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的

In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。

子类重写了父类方法和属性,访问的是父类的属性,调用的是子类的方法

堆用于存储对象实例,只要不断创建对象并保证 GC Roots 到对象有可达路径避免垃圾回收,随着对象数量的增加,总容量触及最大堆容量后就会 OOM,例如在 while 死循环中一直 new 创建实例。

堆 OOM 是实际应用中最常见的 OOM,处理方法是通过内存映像分析工具对 Dump 出的堆转储快照分析,确认内存中导致 OOM 的对象是否必要,分清到底是内存泄漏还是内存溢出。

如果是内存泄漏,通过工具查看泄漏对象到 GC Roots 的引用链,找到泄露对象是通过怎样的引用路径、与哪些 GC Roots 关联才导致无法回收,一般可以准确定位到产生内存泄漏代码的具***置。

如果不是内存泄漏,即内存中对象都必须存活,应当检查 JVM 堆参数,与机器内存相比是否还有向上调整的空间。再从代码检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行期的内存消耗。

**2、**都可以编写多线程程序

Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息 注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。

加载是类加载过程中的一个阶段, 这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象, 作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。

这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:

实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static 指令是程序被编译后, 存放于类构造器方法之中。

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的

实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static 指令是程序被编译后, 存放于类构造器方法之中。但是注意如果声明为:

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:

符号引用与虚拟机实现的布局无关, 引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。

直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。

初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证子方法执行之前,父类的方法已经执行完毕, 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。

注意以下几种情况不会执行类初始化:

**1、**通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。

**2、**定义对象数组,不会触发该类的初始化。

**3、**常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。

**4、**通过类名获取 Class 对象,不会触发类的初始化。

**5、**通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。

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

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

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

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

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

**2、**同时 jstat 查看监控 JVM 的内存和 GC 情况,先观察问题大概出在什么区域

**3、**使用 MAT 工具载入到 dump 文件,分析大对象的占用情况,比如 HashMap 做缓存未清理,时间长了就会内存溢出,可以把改为弱引用

改变了,因为传递是对象的引用,操作的是引用所指向的对象

可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。Runtime.freeMemory() 方法返回剩余空间的字节数,Runtime.totalMemory() 方法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。

普通的对象引用关系就是强引用

软引用用于维护一些可有可无的对象。只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。

弱引用对象相比较软引用,要更加无用一些,它拥有更短的生命周期。当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。

虚引用是一种形同虚设的引用,在现实场景中用的不是很多,它主要用来跟踪对象被垃圾回收的活动。

Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器, G1 收集器两个最突出的改进是:

**1、**基于标记-整理算法,不产生内存碎片。

**2、**可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间, 优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率

String 的intern方法是一个本地方法,作用是如果字符串常量池中已包含一个等于此 String 对象的字符串,则返回池中这个字符串的 String 对象的引用,否则将此 String 对象包含的字符串添加到常量池并返回此 String 对象的引用。

在 JDK6 及之前常量池分配在永久代,因此可以通过-XX:PermSize-XX:MaxPermSize限制永久代大小,间接限制常量池。在 while 死循环中调用intern方法导致运行时常量池溢出。在 JDK7 后不会出现该问题,因为存放在永久代的字符串常量池已经被移至堆中。

**1、**jps,显示系统所有虚拟机进程信息的命令行工具

**2、**jstat,监视分析虚拟机运行状态的命令行工具

**3、**jinfo,查看和调整虚拟机参数的命令行工具

**4、**jmap,生成虚拟机堆内存转储快照的命令行工具

**5、**jhat,显示和分析虚拟机的转储快照文件的命令行工具

**6、**jstack,生成虚拟机的线程快照的命令行工具

**8、**jhsdb,基于服务性代理实现的进程外可视化调试工具,JDK 9 提供

**10、**jvisualvm,图形化虚拟机使用情况的分析工具

**2、**vjtools,唯品会的包含核心类库与问题分析工具

**4、**greys,JVM进程执行过程中的异常诊断工具

**9、**BTrace,基于动态字节码修改技术(Hotswap)实现的Java程序追踪与分析工具

JDK 自带的命令行工具方便快捷,不是特别复杂的问题可以快速定位;

阿里的 arthas 命令行也不错;

如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)

在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量, Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器, 如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和年老代 Parallel Old

**2、**就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

**3、**运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

**4、**阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。

根据阻塞产生的原因不同,阻塞状态又可以分为三种:

**1、**等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

**2、**同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

**3、**其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

**5、**死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

类加载器具有等级制度但非继承关系,以组合的方式复用父加载器的功能。双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父加载器。

一个类加载器收到了类加载请求,它不会自己去尝试加载,而将该请求委派给父加载器,每层的类加载器都是如此,因此所有加载请求最终都应该传送到启动类加载器,只有当父加载器反馈无法完成请求时,子加载器才会尝试。

类跟随它的加载器一起具备了有优先级的层次关系,确保某个类在各个类加载器环境中都是同一个,保证程序的稳定性。

01、java中有几种方法可以实现一个线程?

04、String类的常用方法有那些?

05、请你谈谈对OOM的认识

07、Java 中如何格式化一个日期?如格式化为 ddMMyyyy 的形式?

08、什么是Java虚拟机

09、Java 中的同步集合与并发集合有什么区别?

10、适配器模式和代理模式之前有什么不同?

11、说说G1垃圾收集器的工作原理

13、当父类引用指向子类对象的时候,子类重写了父类方法和属性,那么当访问属性的时候,访问是谁的属性?调用方法时,调用的是谁的方法?

17、怎么打破双亲委派模型?

19、你有哪些手段来排查 OOM 的问题?

20、假设把实例化的数组的变量当成方法参数,当方法执行的时候改变了数组内的元素,那么在方法外,数组元素有发生改变吗?

21、怎么获取 Java 程序使用的内存?堆使用的百分比?

22、强引用、软引用、弱引用、虚引用是什么?

24、运行时常量池溢出的原因?

25、Java最顶级的父类是哪个?

26、JVM 监控与分析工具你用过哪些?介绍一下。

09、Java线程具有五中基本状态

30、双亲委派模型是什么?

31、什么是方法内联?

32、你对线程优先级的理解是什么?

33、Java是否需要开发人员回收内存垃圾吗?

34、说说Java 垃圾回收机制

37、JVM 有哪些运行时内存区域?

40、什么是建造者模式

42、常见的计算机网络协议有那些?

43、一个线程运行时发生异常会怎样?

44、遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?

48、说一下 JVM 调优的工具?

49、JVM 提供的常用工具

51、有哪些类加载器?

54、如何判断对象是否是垃圾?

55、volatile 类型变量提供什么保证?

56、在 Java 程序中怎么保证多线程的运行安全?

57、线上常用的 JVM 参数有哪些?

60、使用js获取一个表单元素

61、Sql优化有那些方法?

63、在 Java 中,对象什么时候可以被垃圾回收?

65、重排序实际执行的指令步骤

66、Java中异常分为哪两种?

67、什么是并发容器的实现?

68、创建线程的四种方式

69、Java 中,直接缓冲区与非直接缓冲器有什么区别?

如果不背 Java面试题的答案,肯定面试会挂!

这套Java面试题大全,希望对大家有帮助哈~

你给出的两个字符串对象体现不这两种定义的区别,当然也是有区别的。

指的是在编译期确定,并被保存在已编译的字节码文件中的一些数据,它包括类、方法、接口等中的常量,也包括字符串常量。

2.==: 比较是否是同一个对象

3.equals(): 比较的是对象里的内容

原因:编译时,这两个"abc"被认为是同一个对象保存到了常量池中;运行时JVM则认为这两个变量赋的是同一个对象,所以返回true。

原因:用构造器创建的对象,是不会被放入常理池中的,也很明显这完全是两个对象,只是内容相同罢了,结果当然为false了。用equals()或者System.out.print(a.intern()==b.intern());就返回true了。

原因:运行出现的字符串常量,若是在常量池中出现过,则JVM会认为同一个对象,以节省内存开销,所以这两个字符串会被认为是同一个对象。

原因:编译时,先将"abcedf"放在常量池中,而c的值则是在运行时在堆里创建的。所以为false。

对象优先在 Eden 区分配

  • 大多数情况下,对象在新生代Eden区中分配,当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC
  • 指发生在新生代的垃圾收集,因为 Java对象大多朝生夕灭,所以 Minor GC非常频繁, 一般回收速度也比较快
  • 大对象就是指需要大量连续内存空间的 Java 对象如字符串、数组,为了避免为大对象分配内存时由于分配担保
    机制带来的复制而降低效率

长期存活的对象进入老年代

  • 对象头的 Age 属性记录年龄,对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor
    容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1,对象在 Survivor 中每熬过一次 MinorGC,年龄
    就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中
  • 并不一定要Age到达阈值才晋升到老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半, 年龄大于或等于该年龄的对象就可以直接进入老年代
  • Minor GC 之前,Minor GC 检查机制检查发现老年代可用内存小于新生代对象大小,此时如果开启了空间分配担保机制并且老年代的连续空间大于历次晋升到老年代对象的平均大小,就会进行 MinorGC,否则将进行 Full GC
    1. 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;目前只有 CMS 收集器会有单独收集老年代的行为
    2. 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集 目前只有 G1
  • 引用计数法:有地方引用它,计数器加一,引用失效,计数器减一 简单高效但是不能解决循环引用问题
  • 可达性分析算法:通过一系列的称为 GC Roots 的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,如果某个对象到 GC Roots 间没有任何引用链相连,或者用说从 GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的

CMS 用增量更新 ,G1用原始快照来进行可达性分析,因为要保证在一致性的快照上进行对象图的遍历(三色图)

  • 可达性分析算法中,从GC Roots 集合找引用链时,为了避免从所有 GCRoots 候选位置中进行根节点枚举,使用OopMap 数据结构来直接得到什么地方存放着对象引用,并不需要真正一个不漏地从方法区等GC Roots开始查找,缩短了根节点枚举时间

OopMap 存储哪两种对象引用

  • 对象内的引用 在类加载完的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来
  • 栈、寄存器中引用 在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用
  • 可达性分析算法必须是在一个确保一致性的内存快照中进行。如果在分析的过程中对象引用关系还在不断变化,分析结果的准确性就不能保证。安全点意味着在这个点时,所有工作线程的状态是确定的,JVM 就可以安全地执行 GC
  • 能让程序长时间执行的地方,这些指令代表着我们的代码将长时间不会继续往下执行,避免GC等待太久,就应该在这些地方设立安全点
  • 长时间执行 的最明显特征就是指令序列的复用, 例如方法调用、 循环跳转、 异常跳转等都属于指令序列复用

如何将线程停止在安全点

  • 抢先式中断 系统中断所有用户线程,没到安全点的,恢复它,让其继续运行到安全点
  • 主动式中断 在安全点设置一个标志,不断轮询这个中断标志,标志为真,就停在安全点指令
  • 安全点存在的 因线程阻塞或休眠,无法响应虚拟机的中断请求,走到安全的地方去中断挂起自己的问题
  • 安全点的拓展,是指能够确保在某一段代码片段之中,引用关系不会发生变化,这个区域内,任何地方垃圾回收都是安全的,解决了上述问题

可达性分析判定为不可达对象一定被回收吗

  • 不一定,如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行 finalize() 方法,当对象没有覆盖 finalize() 方法或者已被虚拟机调用过,那么就认为是没必要的,如果该对象有必要执行 finalize() 方法,那么这个对象将会放在一个称为 F - Queue 的队列中,虚拟机会触发一个 Finalizer 线程去执行它们的 finalize() 方法 ,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是因为如果 finalize() 执行缓慢或者发生了死锁,那么就会造成 F-Queue 队列一直等待,造成了内存回收系统的崩溃。之后 GC 对处于 F-Queue 中的对象进行第二次被标记,这时,如果对象已经在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象建立关联即可,如把自己 (this关键字) 赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出 “即将回收” 的集合,如果没有被移出集合,就会被回收

通过三色标记算法解释 为什么要在一致性的快照上进行对象图的遍历

  • 白色: 对象还未访问,最后还是白色说明对象不可达
  • 黑色: 可达对象 没有引用没扫描过
  • 灰色: 已经被扫描过了但还有引用没扫描过

用户线程与收集线程并行,导致产生浮动垃圾,或则错误回收存活对象 (致命)

  1. 插入黑色对象到白色对象引用
  2. 删除所有灰色对象到白色对象的直接或间接引用
  1. 插入的记下来,黑色变灰色 增量更新
  2. 删除的也会按没删除搜索 原始快照
  • 下面所列举出来的对象,指向它们的引用,就可以作为 GC Roots
  • 虚拟机栈 (栈帧中的局部变量表) 中引用的对象、本地方法栈(Native 方法) 中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、所有被同步锁持有的对象

Java 内存分配是如何保证线程安全的

  • CAS + 失败重试 冲突失败就重试,直到成功为止
  • 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
  • 第一部分用于存储对象自身的运行时数据 哈希码、GC 分代年龄、锁状态标志等
  • 如果是数组类型,还有数组的长度
  • 对象的大小必须是 8 字节的整数倍

Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息

reference 中存储的直接就是对象地址,对象的内存布局就必须考虑如何放置访问类型数据的相关信息

  • 使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销

**Class文件常量池主要存放两大常量:**字面量和符号引用,字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量:

执行引擎在执行字节码的时候通常有哪两种方式

  • 通常会有解释执行 (通过解释器执行) 和 编译执行 (通过即时编译器产生本地代码执行)
  • 解释执行引擎是基于栈的执行引擎,一个个方法对应一个个栈帧,每个方法的执行和结束对应着栈帧的入栈和出栈

基于栈的执行引擎与基于寄存器的执行引擎比较

    1. 基于栈,可移植,不受硬件约束
    2. 编译器实现简单,因为只在栈上操作
    1. 完成相同功能指令条数多
    2. 频繁访内,虽然可以通过栈顶缓存优化,把最常用操作映射到寄存器中避免访内,但不能解决本质问题
  • 栈帧存储了方法的局部变量表、 操作数栈、 动态连接和方法返回地址等信息
  • 存放方法参数与方法内定义的局部变量
  • 在Java程序被编译为 Class文件时,就在方法的Code属性的 max_locals 数据项中确定了该方法所需分配的局部变量表的最大容量

局部变量表和操作数栈的可以重叠

  • 两个不同栈帧相互独立,但是上面栈帧的局部变量表可以与下面栈帧的操作数栈重复,可以节约空间,还可以在进行方法调用时就可以直接共用一部分数据,无须进行额外的参数复制传递
  • 指向运行时常量池中该栈帧所属方法的符号引用,以支持方法调用过程中的动态链接
  • (1) 正常调用完成 (2) 执行过程中遇到了异常并且在方法内无法处理,无返回值

  • (1)方法正常退出 栈帧会保存主调方法 PC 计数器值作为返回地址 (2)方法异常退出时,返回地址是通过异常处理器表来确定

  • 只是确定调用的版本而不是执行
  • 解析调用是个静态的过程, 在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用全部转变为明确的直接引用
  • 在编译完成就能确定版本的方法(非虚方法),invokestatic 静态方法;invokespecial 调用实例构造器 < init > 方法、私有方法、父类中的方法; invokevirtual (被final修饰的实例方法),这些方法在类加载解析阶段就会把符号引用转直接引用

JVM支持的方法调用字节码指令

  • invokeinterface 用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用
  • invokedynamic 先在运行时动态解析出调用点限定符所引用的方法, 然后再执行该方法

前面4条调用指令, 分派逻辑都固化在 Java 虚拟机内部, 而 invokedynamic 指令的分派逻辑是由用户设定的引导方法来决定的

静态分派 — 方法重载

编译器对于重载方法的选择,是根据传入参数的静态类型来选择的,在编译器结束就确定了方法的版本,实例方法和静态方法的重载的都是通过静态分派来确定调用版本的

为什么返回值不能作为方法重载依据

  • Java 语言要重载一个方法,则必须要与原方法具有相同方法名和不同的特征签名,Java 代码层面,返回值并未包含在特征签名之中,但是JVM字节码层面,受查异常表和返回值也包含在特征签名中

动态分派 — 方法重写

  • 通过 invokevirtual 指令,invokevirtual 会找到操作数栈栈顶的第一个元素的所指向对象的实际类型,先在实际类型中寻找匹配的方法,后到父类中寻找,找不到抛出AbstractMethodError

避免频繁地去搜索合适的目标方法

动态分派是通过虚表实现的,方法有虚方法表,接口方法有虚接口方法表,通过索引来找到各个方法的实际入口地址,父类和子类都有自己虚表,如果子类重写了父类的方法,那么子类的虚方法表中,入口会指向子类实现方法的版本的入口,没有实现则指向父类实现的方法版本入口

  • 宗量 —— 方法的接收者、方法参数 称为宗量
  • 静态分派属于静态多分派,由方法的接收者的静态类型 与 方法参数的静态类型 两个宗量确定;动态分派由接收者的实际类型确定,属于单分派

插入注解处理器、解析与填充符号表(词法分析、语法分析、填充符号表) 、提前处理注解、解析与填充符号表、语义分析与生成字节码

  • 保证 final 的不变性只在编译器,编译完后,final变量与普通变量无异
  • 生成字节码 添加 实例构造器( {},实例变量初始化,调用父类的实例构造器) ,类构造器 (static {},类变量初始化,
    调用父类的实例构造器 ) ,保证先执行父类的实例构造器,然后初始化变量,最后执行语句块

如果用户代码中没有提供任何构造函怎么办

  • 在填充符号表阶段将会添加一个没有参数的、可访问性 (public、 protected、 private或 package ) 与 当前类型一致的默认构造函数

为什么 HotSpot 虚拟机要使用解释器与即时编译器并存的架构

  • 解释执行可以使程序可以迅速启动和执行
  • 随着时间的推移, 即使编译器把越来越多的代码编译成本地代码, 这样可以减少解释器的中间损耗,获得更高的执行效率

**为何HotSpot虚拟机要实现两个不同的即时编译器 **

  • HotSpot 虚拟机可以根据自身版本与宿主机器的硬件性选择适合的即时编译器,当然用户也可以使用 “-client"或”-server"参数去强制指定虚拟机运行在Client模式或Server模式

分层编译的好处:解释执行不需收集监控信息,客户端编译执行以更高的编译速度给服务端编译提供更多的时间

jdk7 服务端模式默认使用默认使用分层编译
level 3 C1 编译执行,开启全部性能监控,收集分支跳转,虚方法调用版本
level 4 C2 编译执行,将字节码编译为本地代码,耗时更长,性能更优秀,根据监控信息激进优化。

哪些程序代码会被编译为本地代码

  • 编译发生在方法执行的过程中所以被称为栈上替换 —— 方法的栈帧还在栈上,方法就被替换了

**程序何时使用解释器执行,何时使用编译器执行 **

  • 当一个方法被调用或解释器遇到一条回边指令时,会先检查该方法是否存在被 JIT 编译过的版本,如果存在,则优先使用编译后的本地代码来执行,对方法调用计数器或回边计数器进行相应操作后,如果到达触发即时编译条件,提交即时编译请求,后继续以解释执行,当即时编译完毕后以编译器执行

**如何判断是否是热点代码,需要触发即时编译 **

  • HotSpot 采用的是 是基于计数器的热点探测
逃逸分析原理:动态分析对象作用域
分为:不会逃逸 方法逃逸(作为参数传递) 线程逃逸
方法逃逸: 作为参数传递到其他方法中
线程逃逸:别的线程能访问到
 
栈上分配 在栈上分配对象内存,随栈帧出栈而销毁 支持方法逃逸
标量替换 如果一个对象可以被分解 且完全不会逃逸,可以把对象拆分成原始类型栈上分配,为其他优化垫基
同步消除 没有线程逃逸,就没有竞争,就没有同步的必要 

在正式 Minor GC 前,JVM 会先检查新生代中对象,是比老年代中剩余空间大还是小。假如 Minor GC之后 Survivor 区放不下剩余对象,这些对象就要进入老年代,老年代剩余空间大于新生代中的对象大小, 那就直接 Minor GC, GC 完 Survivor 不够放,老年代也绝对够放;老年代剩余空间小于新生代中的对象大小,这个时候就要查看是否启用了老年代空间分配担保规则

  • 在 Java 堆中没有内存完成实例分配,并且堆也无法再扩展时, Java 虚拟机将会抛出 OutOfMemoryError 异常
  • 不断创建对象,并且 GC Roots 到对象之间有可达路径,对象无法被回收
  • 使用内存映像分析工具 Jprofiler,对 Dump 出来的堆储存快照进行分析,分析清楚是内存泄漏还是内存溢出
  • 如果是内存泄漏,可进一步通过工具查看泄漏对象到 GC Roots 的引用链,修复应用程序中的内存泄漏
  • 如果不存在泄漏,可以考虑用 -Xmx 增加堆大小 ,从代码上检查是否存在有些对象生命周期过长等问题

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常; 如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError 异常

  • 栈帧太大或是虚拟机栈容量太小,当新的栈帧内存无法分配的时候,抛出 StackOverflowError 异常
  • 不断地建立线程的方式会导致内存溢出
  • 如果是 OutOfMemoryError,检查是否有死循环创建线程等,通过 -Xss 降低的每个线程栈的容量

方法区无法满足新的内存分配需求,或者当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常

  • 使用CGLib 生成了大量的代理类,导致方法区被撑爆
  • 检查是否使用 CGLib 生成了大量的代理类

直接内存也被频繁地使用,也可能导致OOM

  • 本机直接内存的分配虽然不会受到 Java 堆大小的限制,但是受到本机总内存大小限制
  1. 使用 top 命令找出 cpu 占用最高的进程
  2. jstack 导出进程当前时刻的线程快照到文件
  3. 最后用 cat 命令结合 grep 命令对十六进制线程 PID 进行过滤,可以定位到出现问题的代码
  • jstat 监视虚拟机各种运行状态信息
  • jinfo 实时查看和修改虚拟机参数,不需要重启
  • jstack 生成虚拟机当前时刻的线程快照
  • GC调优目的:GC时间够少, GC次数够少
  1. -Xss 每个线程栈空间设置

  2. 开启GC日志对性能影响很小且能帮助我们定位问题

  • full gc 频繁说明 old 区很快满了,如果是一次 full gc 后,剩余对象不多,那么说明你 eden 区设置太小,导致短生命周期的对象进入了old 区。如果一次 full gc 后,old 区回收率不大,那么说明 old 区太小

5. jstack 导出进程当前时刻的线程快照到文件
6. 最后用 cat 命令结合 grep 命令对十六进制线程 PID 进行过滤,可以定位到出现问题的代码

  • jstat 监视虚拟机各种运行状态信息
  • jinfo 实时查看和修改虚拟机参数,不需要重启
  • jstack 生成虚拟机当前时刻的线程快照
  • GC调优目的:GC时间够少, GC次数够少
  1. -Xss 每个线程栈空间设置

  2. 开启GC日志对性能影响很小且能帮助我们定位问题

  • full gc 频繁说明 old 区很快满了,如果是一次 full gc 后,剩余对象不多,那么说明你 eden 区设置太小,导致短生命周期的对象进入了old 区。如果一次 full gc 后,old 区回收率不大,那么说明 old 区太小

我要回帖

更多关于 java不是内部命令也不是可运行程序 的文章

 

随机推荐