u 程序计数器html
u Java栈(虚拟机栈)java
u 本地方法栈算法
u Java堆编程
u 方法区及其运行时常量池数组
u 新生代和老年代安全
u 参数设置数据结构
u 垃圾回收(Minor GC 和 Full GC)和回收算法多线程
u finalize()、减小GC开销、触发主GC的条件jvm
u 判断无用对象、四种引用方式、为何进行垃圾回收ui
u String、StringBuffer与StringBuilder
u 类加载机制
第1、程序计数器(PC)
程序计数器(Program Counter Register)是一块较小的内存空间,它能够看作当前线程所执行的字节码的行号指示器,字节码解释器工做时就是经过改变这个计数器的值来取下一条须要执行的字节码指令
因为Java 虚拟机的多线程是经过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个肯定的时刻,一个处理器(对于多核处理器来讲是一个内核)只会执行一条线程中的指令。所以,为了线程切换后能恢复到正确的执行位置,每条线程都须要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,咱们称这类内存区域为“线程私有”的内存。
若是线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若是正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。
注:程序计数器是线程私有的,每条线程都会有一个独立的程序计数器
第2、Java栈(虚拟机栈)
Java栈就是Java中的方法执行的内存模型,每一个方法在执行的同时都会建立一个栈帧(关于栈帧后面介绍),这个栈帧用于存储局部变量表、操做数栈、动态连接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
注:Java栈也是线程私有的。
异常可能性:对于栈有两种异常状况:若是线程请求的栈深度大于栈所容许的深度,将抛出StackOverflowError异常,若是虚拟机栈能够动态拓展,在拓展的时没法申请到足够的内存,将会抛出OutOfMemoryError异常
栈帧
1) 局部变量表
2) 操做数栈
3) 整数加法
4) 法返回地址
第3、本地方法栈
本地方法栈与Java栈所发挥的做用是很是类似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法。
注:本地方法栈也是线程私有的
异常可能性:和Java栈同样,可能抛出StackOverflowError和OutOfMemeryError异常
第4、Java堆
对于大多数应用来讲,Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时建立。此内存区域的惟一目的就是存放对象实例,几乎全部的对象实例都在这里分配内存,固然咱们后面说到的垃圾回收器的内容的时候,其实Java堆就是垃圾回收器管理的主要区域。
注:堆是线程共享的
异常可能性:若是堆中没有内存完成实例分配,而且堆也没法再拓展时,将会抛出OutOfMemeryError异常
第5、方法区(若是一个系统不断地产生新的类,而没有回收,那最终很是有可能致使永久区溢出。)
方法区它用于存储已被虚拟机加载的类信息(类型信息,字段信息,方法信息,其余信息)静态量、即时编译器编译后的代码等数据。方法区是线程安全的
注:方法区和堆同样是线程共享的
异常可能性:当方法区没法知足内存分配需求时,将抛出OutOfMemeryError异常
运行时常量池
用于存放编译器生成的各类字面量和符号引用当常量池没法再申请到内存时就会抛出OutOfMemoryError异常。
问:在方法里面建立的本地对象,它会建立在内存结构的哪一个地方?如何访问该对象?
在Java栈的栈帧里面建立了对象的引用,在堆上建立了对象,栈帧里的引用指向堆中的对象。
参数配置
-Xss是设置栈的
Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间。
堆的内存分配
新生代(Young Generation)(默认的Eden:Survivor = 8:1)
1.全部新生成的对象首先都是放在新生代的。新生代的目标就是尽量快速的收集掉那些生命周期短的对象。
2.新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(通常而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,而后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另外一个survivor1区,而后清空eden和这个survivor0区,此时survivor0区是空的,而后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
3.当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。如果老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收
4.新生代发生的GC也叫作Minor GC,MinorGC发生频率比较高(不必定等Eden区满了才触发)。通常把老年带发生的gc叫full GC
在Minor GC时会将新生代中还存活着的对象复制进一个Survivor中,而后对Eden和另外一个Survivor进行清理。因此,日常可用的新生代大小为Eden的大小+一个Survivor的大小。全部的Minor GC都会触发全世界的暂停(stop-the-world除了垃圾收集收集器线程以外的线程都被挂起),中止应用程序的线程,不过这个过程很是短暂。Eden 和Survivor区不存在内存碎片。
当对象在 Eden出生后,在通过一次 Minor GC 后,若是对象还存活,而且可以被另一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另一块 Survivor 区域 ( 即 to 区域 ) 中,而后清理所使用过的 Eden 以及 Survivor 区域 ( 即from 区域 ),而且将这些对象的年龄设置为1,之后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,能够经过参数 -XX:MaxTenuringThreshold 来设定),这些对象就会成为老年代。
对于一些较大的对象 ( 即须要分配一块较大的连续内存空间 ) 则是直接进入到老年代。
在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)
年老代(Old Generation)
1 . 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。所以,能够认为年老代中存放的都是一些生命周期较长的对象。
2 . 内存比新生代也大不少(大概比例是1:2),当老年代内存满时触发Full GC 发生频率比较低,老年代对象存活时间比较长,存活率标记高。
什么状况下,新生代的对象会进入老年代呢?
当Minor GC时,新生代存活的对象大于Survivor的大小时,这时一个Survivor装不下它们,那么它们就会进入老年代。
在新生代的每一次Minor GC 都会给在新生代中的对象+1岁,默认到15岁时就会重新生代进入老年代-XX:MaxTenuringThreshold来设置这个临界点。
若是设置了-XX:PretenureSizeThreshold3M 那么大于3M的对象就会直接就进入老年代。
3.finalize()方法何时被调用?
l 垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法 可是在Java中很不幸,若是内存老是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然期望它作收尾工做是靠不住的。 那么finalize()到底是作什么的呢?
l 它最主要的用途是回收特殊渠道申请的内存。因为垃圾回收器只知道那些显示地经由new分配的内存空间,因此它不知道该如何释放这块“特殊”的内存区域,那么这个时候java容许在类中定义一个由 finalize()方法
问:.减小GC开销的措施
(1)不要显式调用System.gc()
(2)尽可能减小临时对象的使用
(3)对象不用时最好显式置为Null
(4)尽可能使用StringBuffer,而不用String来累加字符串
问:.何时会进行GC(Garbage Collector)
(1)当应用程序空闲时,即没有应用线程在运行时,GC会被调用。由于GC在优先级最低的线程中进行,因此当应用忙时,GC线程就不会被调用,但如下条件除外。
(2)主动调用system.gc()
(2)eden区满时、老年代满时
问:下降GC的调优
可调试NewSize、permSize、NewRatio、SurvivorRatio、进入老年带的临界岁数
Java对象在内存中的状态:
可达的/可触及的:
Java对象被建立后,若是被一个或多个变量引用,那就是可达的。即从根节点能够触及到这个对象。
其实就是从根节点扫描,只要这个对象在引用链中,那就是可触及的。
可恢复的:
Java对象再也不被任何变量引用就进入了可恢复状态。
在回收该对象以前,该对象的finalize()方法进行资源清理。若是在finalize()方法中从新让变量引用该对象,则该对象再次变为可达状态,不然该对象进入不可达状态
不可达的:
Java对象不被任何变量引用,且系统在调用对象的finalize()方法后依然没有使该对象变成可达状态(该对象依然没有被变量引用),那么该对象将变成不可达状态。
当Java对象处于不可达状态时,系统才会真正回收该对象所占有的资源。
两种最基本的垃圾收集器
一、Serial收集器:(串行收集器)
这个收集器是一个单线程的收集器,但它的单线程的意义并不只仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工做,更重要的是在它进行垃圾收集时,必须暂停其余全部的工做线程(Stop-The-World:将用户正常工做的线程所有暂停掉),直到它收集结束。收集器的运行过程以下图所示:
二、ParNew收集器:Serial收集器的多线程版本(使用多条线程进行GC)
ParNew收集器是Serial收集器的多线程版本。
引用计数法
给对象中添加一个引用计数器,任什么时候刻计数器为0的对象就是不可能再被使用的。主流的java虚拟机并无选用引用计数算法来管理内存,其中最主要的缘由是:它很难解决对象之间相互循环引用的问题。
可达性分析法(经常使用)
该方法的基本思想是经过一系列的根对象的集合做为起点进行搜索,搜索所通过的路径称为“引用链”,从这些根对象开始,任何能够被触及的对象都是被认为是“活动”的对象。没法被触及的对象被认为是垃圾,由于它们不在影响程序的将来执行。在后面介绍标记-清理算法/标记整理算法时,也会一直强调从根节点开始,对全部可达对象作一次标记。
可做为GC Roots的对象包括下面几种:
1.Mark-Sweep(标记-清除)算法
这是最基础的垃圾回收算法,之因此说它是最基础的是由于它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出全部须要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
从图中能够很容易看出标记-清除算法实现起来比较容易,可是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会致使后续过程当中须要为大对象分配空间时没法找到足够的空间而提早触发新的一次垃圾收集动做。
2.Copying(复制)算法
为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。问题是占用内存较多
3.Mark-Compact(标记-整理)算法
为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep同样,可是在完成标记以后,它不是直接清理可回收对象,而是将存活对象都向一端移动,而后清理掉端边界之外的内存。
4.分代收集算法
通常是把Java堆分为新生代和老年代,这样就能够根据各个年代的特色采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,那就选用复制算法,只须要付出少许存活对象的复制成本就能够完成收集。而老年代中由于对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。方法区永久代,回收方法同老年代。
老年代采用的是标记-清除或者标记-整理算法,这两个算法主要看虚拟机采用的哪一个收集器,两种算法的区别是:标记-清除可能会产生大量连续的内存碎片。
String 字符串常亮,底层是数组实现的
StringBuilder:线程非安全的
StringBuffer:线程安全的
对于三者使用的总结:
1.若是要操做少许的数据用 = String
2.单线程操做字符串缓冲区下操做大量数据 = StringBuilder
3.多线程操做字符串缓冲区下操做大量数据 = StringBuffer
四.为何要进行垃圾回收
在C++中,对象所占的内存在程序结束运行以前一直被占用,在明确释放以前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。 JVM的一个系统级线程会自动释放该内存块,减轻编程的负担。事实上,除了释放没用的对象,垃圾回收也能够清除内存记录碎片。
五.java虚拟机类加载机制
包括加载、连接(含验证、准备、解析)、初始化
其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。
l 加载
加载时类加载过程的第一个阶段,在加载阶段,虚拟机须要完成如下三件事情:
一、经过一个类的全限定名来获取其定义的二进制字节流。
二、将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构。
三、在Java堆中生成一个表明这个类的java.lang.Class对象,做为对方法区中这些数据的访问入口。
对于任意一个类,都须要由它的类加载器和这个类自己一同肯定其在就Java虚拟机中的惟一性
类加载器:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器
双亲委派模型:若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,所以,全部的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即没法完成该加载,子加载器才会尝试本身去加载该类。(Java中的Object类,不管哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,所以Object在各类类加载环境中都是同一个类。)
l 验证
验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
l 准备
准备阶段是正式为类变量分配内存并设置类变量初始值(零)的阶段,这些内存都将在方法区中分配。
l 解析
解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程。
l 初始化
初始化是类加载过程的最后一步,此阶段才开始真正执行类中定义的Java程序代码(静态语句块和构造器)
类的初始化过程(重要)
Student s = new Student();在内存中作了哪些事情?
强引用、软引用、弱引用、虚引用
l 强引用:当咱们使用new 这个关键字建立对象时被建立的对象就是强引用,垃圾回收器就不会去回收有强引用的对象。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具备强引用的对象来解决内存不足的问题。
l 软引用:若是一个对象具有软引用,若是内存空间足够,那么垃圾回收器就不会回收它,若是内存空间不足了,就会回收该对象。固然没有被回收以前,该对象依然能够被程序调用。java.lang.ref.SoftReference
l 弱引用:若是一个对象只具备弱引用,只要垃圾回收器在本身的内存空间中线程检测到了,就会当即被回收,对应内存也会被释放掉。java.lang.ref.WeakReference
l 虚引用:若是一个对象只具备虚引用,那么它就和没有任何引用同样,随时会被jvm看成垃圾进行回收。虚引用目的:当对象被收集器回收时收到系统通知。java.lang.ref.PhantomReference