重点章节
代码运行过程
Java 源文件—->编译器—->字节码文件
字节码文件—->JVM—->机器码
自动分配内存
内存分配的两种方式:
空闲列表
指针碰撞
线程共享的:
- 堆:对象
- 方法区:类信息
线程独享的:
- 程序计数器 pc
- 本地方法栈
- 虚拟机栈
生命周期
-
线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁
-
线程共享区域随虚拟机的启动/关闭而创建/销毁
常量池
类常量池,字符串常量池。
一直存在,减少重复的创建和销毁,作用类似于缓存,提升速度。
直接内存
操作系统不能直接把数据交给虚拟机,只能虚拟机交给操作系统,操作系统可以有放到操作系统内存。
频繁的io操作会让直接内存和本地内存占满。
对象创建过程
对象头有类信息和锁信息。
- 内存泄漏,对象创建太多了。
- 栈溢出,方法创建太多了。
dump:一瞬间的内存状态形成文件。
运行时内存
垃圾回收机制
如何判断对象以及变成垃圾(面试重点)
-
引用计数法,记录被引用的次数,但是有缺陷,相互引用的无法垃圾无法清空。
-
可达性分析,是现在java用的。
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
引用方式(面试重点)
引用分为强引用(Strongly Re-ference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)
-
强引用 是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Objectobj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
-
软引用 是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用。
-
弱引用 也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference类来实现弱引用。
-
虚引用 也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供了PhantomReference类来实现虚引用。
虚引用的主要作用是跟踪对象被垃圾回收的状态
TreadLocal 就是用的 弱引用,内部数据只有自己看的到,所以内存消耗大。
垃圾回收算法(重点,重点)
是针对于堆区的。
当前版本使用的G1, 混合搜集。
混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。
枚举根节点时,g1会分区域,不会stop the world 停掉所以线程,但是枚举根节点时,任然要停顿,无法避免。
无论回收那一区域,对应线程都要停止,所以如何降低冲突很重要。
所以产生了不同的垃圾回收器,和垃圾回收算法。
有哪些垃圾回收算法,哪些垃圾收集器(面试点)
垃圾回收器
标记-清除
内存碎片化严重
标记-复制
一般用于年轻代
内存被压缩到了原本的一半。
标记-整理
一般用于年老代
卡表
与操作系统分页原理相同。
写屏障,操作对应内存时不能垃圾回收此内存而已。
经典垃圾收集器
15岁,大对象,进入老年代。
年轻代内存不足也会进入老年代。
CMS收集器(面试重点)
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark)
- 并发清除(CMS concurrent sweep)
特点 :并发收集,低停顿。
多核处理器才会变快,毕竟是并发,单核反而会满。
G1收集器(面试重点)
分区域收集
所以可以设置回收时间。
局部出现等待
在内存大的情况下比cms表现要好,
如数组和红黑树。 数据量大采用数据结构。
-
初始标记(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的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行
完成的。
规整碎片时,和所以和此区域内存相关的都有关系,免不了stop the world.
现在没有收集器可以解决碎片化问题。
内存分配和回收策略
jvm调优参数
对象优先在Eden分配
年轻代: 两个Survivor,一个eden,其中国一个 s 和 e 可以创建对象,然后垃圾回收时,将剩下的对象放入另一个未使用的 s, 此 s 和 e 又作为可以创建对象的,一直循环下去此过程。
垃圾回收: 空间满了 或者 对象满15岁。也可以手动,有一定延迟。
- -XX:+PrintGCDetails这个收集器日志参数
- -Xms20M、-Xmx20M、-Xmn10M这三个参数限制了Java堆大小为20MB,不可扩展,其中 10MB分配给新生代,剩下的10MB分配给老年代。
- -XX:Survivor-Ratio=8决定了新生代中Eden区与一个Survivor区的空间比例是8 ∶ 1 : 1
对象判定
-
大对象直接进入老年代
-
长期存活的对象将进入老年代
-
动态对象年龄判定
同代对象过多,也直接进入老年代。
空间分配担保
解释一下“冒险”是冒了什么风险:前面提到过,新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况——最极端的情况就是内存回收后新生代中所有对象都存活,需要老年代进行分配担保,把Survivor无法容纳的对象直接送入老年代,这与生活中贷款担保类似。老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,但一共有多少对象会在这次回收中活下来在实际完成内存回收之前是无法明确知道的,所以只能取之前每一次回收晋升到老年代对象容量的平均大小作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。
调优指令
jps 虚拟机进程状况工具
可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(LVMID,Local Virtual Machine Identifier)
jstat 虚拟机统计信息监视工具
监视虚拟机各种运行状态信息的命令行工具.
例如:jstat -gcutil:
jinfo Java配置信息工具
jinfo(Configuration Info for Java)的作用是实时查看和调整虚拟机各项参数。
jmap:Java内存映像工具
jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件)。
jhat:虚拟机堆转储快照分析工具
JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jma生成的堆转储快照。jhat内置了一个微型的HTTP/Web服务器,生成堆转储快照的分析结果后,可以在浏览器中查看。
jstack Java堆栈跟踪工具
jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。
调优案例分析
第五章,自己看,整理笔记。
类文件结构 (面试少,用到的多。)
AOP,cglib字节码注入技术
虚拟机 类加载 机制
含义: 类 从 硬盘 到 内存
类加载 过程 和 作用?(面试重点)
-
加载:类进入内存、这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口。
-
验证:检测是否符合某种格式、确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求
-
准备:准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段(赋‘零’值)
-
解析:虚拟机将常量池中的符号引用替换为直接引用的过程
-
初始化:用户写的代码,按照用户的逻辑去运行。
-
使用
-
卸载
符号引用与直接引用
那种情况会触发类的初始化?
类第一次使用:
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
使用new关键字实例化对象的时候。
读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候。
调用一个类型的静态方法的时候。 - 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
- 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先
初始化这个主类。 - 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解
析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句
柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。 - 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有
这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
那种情况不会执行初始化
- 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
- 定义对象数组,不会触发该类的初始化。
- 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触
发定义常量所在的类。 - 通过类名获取 Class 对象,不会触发类的初始化。
- 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初
始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。 - 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。
类加载器(类的寻找)
寻找需要的类,并 加载到内存中。
双亲委派
优先级:
各种类加载器之间的层次关系被称为类加载器的“双亲委派模型(ParentsDelegationModel)。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。
- 双亲委派模型:当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。
保证了使用不同的类加载器最终得到的都是同样一个 Object 对象
tomcat破坏了双亲委派。
热部署: 不清空内存的情况下,再次类加载。
双亲委派有使用范围的限制。
面试题
java虚拟机
java虚拟机都有那些部分,每个部分的作用。
javavisual软件,查看jvm使用状况。 再bin目录下。
字符串常量池,1.7在方法去,1.8之后就在堆区了。