学习笔记之JVM
Posted 可持续化发展
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习笔记之JVM相关的知识,希望对你有一定的参考价值。
反射
1、什么是反射?
反射是在运行中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
2、哪里用到反射机制?
JDBC中,利用反射动态加载了数据库驱动程序。
Web服务器中利用反射调用了Sevlet的服务方法。
Eclispe等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。
很多框架都用到反射机制,注入属性,调用方法,如Spring。
3、反射机制的优缺点?
优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。
缺点:对性能有影响,这类操作总是慢于直接执行java代码。
4、动态代理是什么?有哪些应用?
动态代理是运行时动态生成代理类。
动态代理的应用有 Spring AOP数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。
5、怎么实现动态代理?
有JDK 动态代理、 cglib 动态代理、使用 Spring aop 模块完成动态代理功能。
JDK 动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。
6、获取class对象的三种方法
①使用class类的forName()静态方法,参数是某个类的全限定类名。
②调用某个类的class属性获取对应的class对象,比如Person.class。
③调用某个对象的getClass()。
Class对象可以获得该类里的构造器(Constructor对象)、方法(Method对象)、成员变量(Field对象)。通过这些对象来执行对应的方法,调用对应的构造器来创建实例,直接访问并修改对象的成员变量值。
7、如何使用Java的反射? Java反射机制的作用?
生成对象
①使用class对象的newInstance()方法来创建该class对象对应类的实例。要求对应类有默认构造器。
②先用class对象获取指定的constructor对象,再调用constructor对象的newInstance()方法来创建该class对象对应类的实例。
调用方法
通过该class对象的getMethods()或getMethod()方法来获取全部方法或指定方法,然后再调用Method对象的invoke()。
访问成员变量值
通过class对象的getFeilds()或getFeild()方法获取该类的全部成员变量或指定成员变量。
生成动态代理
用Proxy和InvocationHandler创建动态代理。
对象序列化
1、什么叫对象序列化,什么是反序列化,实现对象序列化需要做哪些工作?
对象的序列化:将一个Java对象写入IO流中。
对象的反序列化;从IO流中恢复该Java对象。
对象序列化的目的是将对象保存到磁盘中或允许在网络中直接传输对象。序列化机制允许把内存中的Java对象转换为与平台无关的二进制流。要让该类可序列化,就必须实现这两个接口之一:Serializable和Externalizable。
使用Serializable实现序列化,主要让目标类实现Serializable接口。Serializable接口是一个标记接口,实现该接口无须实现任何方法,它只是表明该类的实例是可序列化的。特点:①系统自动存储必要信息;②Java内建支持,易于实现,只需要实现该接口即可;③性能略差。一般用Serializable。
实现Externalizable接口,特点:①程序员决定存储哪些信息;②仅仅提供两个空方法,必须为这两个方法提供实现;③性能略好。编程复杂
注意:
①对象的类名、实例变量(基本类型、数组、对其他对象的引用)都可以被序列化。方法、类变量、transient实例变量都不会被序列化。
②实现Serializable接口的类如果想让某个实例变量不被序列化,可以用transient修饰。
③保证序列化对象的实例变量也是可序列化的,否则需要使用transient修饰。要不然,该类不可序列化。
④反序列化对象时必须要有序列化对象的class文件。
⑤当通过文件、网络来读取序列化后的对象时,必须按实际写入的顺序读取。
以下情况需要使用对象序列化:
①所有可能在网络上传输的对象的类应该是可序列化的,比如RMI(远程方法调用)过程中的参数和返回值;
②所有需要保存到磁盘里的对象的类必须是可序列化的,比如web应用中需要保存到httpsession或servletcontext属性的Java对象。
类加载
1、谈谈java类的加载过程。描述一下JVM加载class文件的原理机制
类从被加载到JVM中开始,到卸载为止,整个生命周期包括:加载、连接、初始化、使用和卸载这几个阶段。当程序主动使用某个类且该类未被预加载时,系统会进行类加载。类加载的全过程为加载、验证、准备、解析、初始化。
加载阶段,JVM需要做三件事:
①通过类的全限定名获取定义该类的二进制字节流。
②将该字节流所代表的静态存储结构转化为方法区的运行时数据结构。
③在Java堆内存中生成一个对应的class对象,作为方法区该类的各种数据的访问入口。
类的连接可分为:验证、准备、解析。连接阶段负责把类的二进制数据合并到JRE中。验证阶段负责检查被加载的类是否有正确的内部结构,并与其他类协调一致。验证阶段有4个检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。准备阶段负责为类的类变量分配内存,并设置默认初始值(一般是类型的零值,除非final修饰)。解析阶段负责将常量池中的符号引用替换成直接引用。
初始化阶段是执行类构造器<clinit>()方法的过程,负责对类变量进行初始化。直到初始化阶段,JVM才真正开始执行类中编写的Java 代码,将主导权移交给应用程序。指定类变量初始值有两种方式:①声明时指定初始值②用静态初始化块。
JVM初始化一个类有3个步骤:
①如果该类还没有被加载和连接,就先加载并连接。
②如果该类的直接父类还没有被初始化,就先初始化其直接父类。
③如果类中有初始化语句,则系统依次执行这些初始化语句。
类初始化的时机,当Java程序首次通过以下6种方式使用某个类时系统会初始化该类。①创建类的实例;②调用某个类的类方法;③访问某个类的类变量;④使用反射创建某个类的class对象;⑤初始化某个类的子类;⑥直接使用java.exe命令来运行某个主类。
2、一个类的构造器,代码块,静态代码块,成员变量的 的执行顺序。
父静态成员变量p2
父静态代码块1
父静态代码块2
子静态成员变量c2
子静态代码块1
子静态代码块2
父成员变量p1
父代码块1
父代码块2
父构造器
子成员变量c1
子代码块1
子代码块2
子构造器
3、类加载器
类加载器用于实现类的加载动作。比较2个类是否“相等”,只有在这2个类是由同一个类加载器加载的前提下才有意义。
类加载器的种类:①启动类加载器(Bootstrap);②扩展类加载器(Extension)③应用程序类加载器(System\\App);④自定义类加载器。
各种类加载器的层次关系被称为类加载器的双亲委派模型。
使用双亲委派模型组织类加载器之间的关系,一个好处是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。类加载器之间的父子关系通常使用组合关系来复用父加载器的代码。
双亲委派模型优点:
①保证核心库的安全:如果都由自己的加载器加载,那么会存在很多命名空间,会存在很多相同的类,但是无法相互兼容使用(命名空间不同),无法确保核心类被优先加载。
②隔离功能。JVM中通过命名空间相互隔离相同的类。它们可以一同存在,但在不同命名空间中使用。
双亲委派模型的工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
GC
1、既然有GC机制,为什么还会有内存泄露的情况?
理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题。然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。例如hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。
2、Java中为什么会有GC机制呢?
安全性考虑;
减少内存泄露
减少程序员工作量。
3、对于Java的GC,哪些内存需要回收?
JVM会有一个运行时数据区来管理内存。它主要包括5大部分:
1.程序计数器(Program CounterRegister);
2.虚拟机栈(VM Stack);
3.本地方法栈(Native Method Stack);
4.方法区(Method Area);
5.Java堆(Heap)。
而其中程序计数器、虚拟机栈、本地方法栈是每个线程私有的内存空间,随线程而生,随线程而亡。例如栈中每一个栈帧中分配多少内存基本上在类结构确定是哪个时就已知了,因此这3个区域的内存分配和回收都是确定的,无需考虑内存回收的问题。但方法区和Java堆是线程共享的内存区域。一个接口的多个实现类需要的内存可能不一样,我们只有在程序运行期间才会知道会创建哪些对象,这部分内存的分配和回收都是动态的。总而言之,GC主要进行回收的内存是JVM中的方法区和Java堆。
4、Java的GC什么时候回收垃圾?如何判断对象存活
当内存空间还够时,能够保存在内存中;根据引用类型的不同,GC回收时也会有不同的操作:
①强引用(Strong Reference):Object obj=new Object();只要强引用还存在,GC就不会回收掉被引用的对象。
②软引用(Soft Reference):描述一些还有用但非必须的对象。在系统将会发生内存溢出之前,会把这些对象列入回收范围进行二次回收(即系统将会发生内存溢出了,才会对他们进行回收)
③ 弱引用(Weak Reference):程度比软引用还要弱一些。这些对象只能生存到下次GC之前。当GC工作时,无论内存是否足够,都会将其回收(即只要进行GC,就会对他们进行回收。)
④虚引用(Phantom Reference):一个对象是否存在虚引用,完全不会对其生存时间构成影响。虚引用的目的是为了能在该对象被回收时收到一个系统通知。
判断对象常规有两种方法:引用计数算法和可达性分析算法。引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它时计数器加1,引用释放时计数减1,当计数器为0时可以回收。
引用计数算法实现简单,判断高效,在微软COM和Python语言等被广泛使用,但在主流的Java虚拟机中没有使用该方法,主要是因为无法解决对象相互循环引用的问题。
当前垃圾收集器基本上是依靠可达性分析算法判断对象是否存活。可达性分析算法:基本思想是通过一系列称为“GC Root”的根对象作为起点,基于对象引用关系,开始向下搜索,所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连,证明对象是不可用的。
5、哪些对象可以作为GC Root对象
①虚拟机栈中引用的对象;
②方法区中类静态属性引用的对象;如Java类的引用类型静态变量。
③方法区中常量引用的对象;如字符串常量池中的引用。
④本地方法栈中JNI(即通常所说的native方法)引用的对象;
⑤虚拟机内部的引用、系统类加载器;
⑥所有被同步锁(synchronized)持有的对象;
6、Java GC都用了哪些算法?分别应用在什么地方?(标记清楚、标记复制、标记整理具体八股文请翻阅《深入理解JVM虚拟机第三版》)
标记清除算法
标记清除(Mark-Sweep)算法,包含“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。标记清除算法是最基础的收集算法,后续的收集算法大多是基于该思路并对其缺点进行改进而得到的。
主要缺点:第一个是执行效率不稳定,该算法的执行效率会随着垃圾对象数量的增长而降低;第二个是内存空间碎片化问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序需要分配大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记复制算法
半区复制算法……
Appel式回收算法,更优化的半区复制分代策略。……Hotspot虚拟机的serial、parnew等新生代收集器采用了这种策略设计了新生代的内存布局。
标记整理算法
标记过程和标记清除算法一样,之后让所有存活的对象向内存空间的一端移动,然后直接清理掉边界以外的内存。
CMS收集器面临空间碎片过多的处理方法:让虚拟机平时多数时间采用标记清除算法,暂时容忍内存碎片的存在,直到内存碎片影响到对象分配时,再采用标记整理算法清理一次,以获取规整的内存空间。
7、jvm查看gc命令
jstat命令
8、CMS
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。它是基于标记清除算法实现的。整个过程分为4步:
①初始标记。仅仅标记GC Root能直接关联到的对象。需要停顿用户线程。
②并发标记。它是从GC Root直接关联到的对象开始,遍历整个对象图的过程。耗时长,但不用停顿用户线程。
③重新标记。修正并发标记期间,因用户程序继续运行而导致标记产生变动的那部分记录。需要停顿用户线程。
④并发清除。清除所有标记阶段判断死亡的对象,不需要移动存活对象,可与用户线程并发。
总体来说,CMS的回收过程是与用户线程一起并发执行的。
它的优点是并发收集、低停顿。
缺点是:
①CMS收集器对处理机资源非常敏感。当处理机资源不足时,CMS对用户程序的影响会变大,速度会变慢。
②CMS无法处理浮动垃圾,就有可能会导致另一次Full GC的产生。
③由于CMS 是基于标记清除算法实现的收集器,所以收集结束后,会有大量空间碎片的产生。
9、G1收集器
G1 收集器叫Garbage First收集器。G1是面向服务端应用的收集器。为了能替换CMS而诞生,目标是在延迟可控的情况下尽可能提高吞吐量。
G1的回收集的标准是哪块内存的垃圾最多,回收收益最大,就回收那里。这是G1的mixed GC模式。G1的新生代和老年代不是固定的,它们是一系列区域的动态集合。
G1堆内存布局:把连续的Java堆分成多个大小相等的区域,每个区域根据需要扮演新生代的老年区、幸存区或老年代空间。
G1的运作过程分为:
①初始标记。仅仅标记一下GC Root直接关联到的对象,并修改TAMS指针。
②并发标记。从GC Root开始对堆中的对象进行可达性分析,递归扫描堆的对象图,找出要回收的对象。耗时长,可用户程序并发执行。扫描完后,需要重新处理并发时有引用变动的对象。
③最终标记。暂停用户线程,处理并发结束时留下的SATB记录。
④筛选回收。负责更新区域的统计数据,对各个区域的回收价值和成本进行排序,根据用户期望的停顿时间来制定回收计划。可以自由选择任意个区域构成回收集,然后把那部分区域中存活的对象移动到空区域中,再清理掉旧的区域空间。必须暂停用户线程,由多条回收器线程并发完成。
相比CMS,G1的优点:
①可以指定最大停顿时间,分区域的内存布局,按收益动态确定回收集。
②G1不会产生内存空间碎片。
相比CMS,G1的缺点:
①G1的卡表实现更复杂。它的记忆集会占更多的内存空间。
②G1为了回收垃圾而产生的内存占用和程序运行时的执行负载要比CMS高。
总体来说,小内存应用上CMS的表现大概率优于G1,而大内存应用上G1更能发挥优势。
以上是关于学习笔记之JVM的主要内容,如果未能解决你的问题,请参考以下文章