1、java的内存模型
线程私有:程序计数器,虚拟机栈,本地方法栈
线程共享:堆,方法区
程序计数器: 当前线程所执行字节码的行号指示器。没有定义异常。
虚拟机栈: java方法执行的内存模型,每一个java方法在执行的时候都会创建一个栈帧用来存储局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表存放了编译期可知的基本类型和引用类型,局部变量表所需的内存空间在编译期间分配完成,运行期间不会改变大小。操作数栈中存放指令执行时所需要的操作数;动态链接:每个栈帧中都包含一个该栈帧所属方法的引用;方法出口:每一个方法结束无非是正常return或者抛出了Exception并且没有catch语句,在方法执行完之后需要返回到方法被调用的位置,返回时需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。可能抛出StackOverflowError或OutOfMemoryError
本地方法栈: 为native方法服务。可能抛出StackOverflowError或OutOfMemoryError
堆: 虚拟机所管理内存中最大的一块,在虚拟机启动时创建,用于存放对象实例和数组,从垃圾回收的角度看分为新生代、老年代;再细致可分为Eden、FromSurvivor、ToSurvivor空间、老年代。可能出现OutOfMemoryError
方法区: 用来存储虚拟机加载的类信息、常量、静态变量、即时编译后的代码数据。垃圾回收的对象为:常量池的回收和类型的卸载。可能出现
OutOfMemoryError。
运行时常量池(方法区的一部分):用来存放编译期生成的各种字面量(文本字符串、声明为final的常量)和符号引用(类的全限定名、字段和方法的名称和描述符)
还有一块不属于虚拟机运行时数据区的一部分为直接内存:例如nio中使用native函数库直接分配堆外内存,然后通过堆内的引用直接操作这块内存,避免了java堆和native堆的来回复制,可能抛出OutOfMemory异常
与垃圾回收相关的JVM参数:
-Xms/-Xmx堆的初始大小/堆的最大大小
-Xss栈的大小
-Xmn堆中年轻代的大小
-XX:-DisableExplicitGC让System.gc()不产生任何作用
-XX:+PrintGCDetails打印GC的细节
-XX:+PrintGCDateStamps打印GC操作的时间戳
-XX:NewSize/XX:MaxNewSize设置新生代大小/新生代最大大小
-XX:NewRatio可以设置老生代和新生代的比例
-XX:PrintTenuringDistribution设置每次新生代GC后输出幸存者乐园中对象年龄的分布
-XX:InitialTenuringThreshold/-XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值
-XX:TargetSurvivorRatio:设置幸存区的目标使用率
2、类加载的过程
初始化的五种情况:
1.遇到new、getStatic、putStatic、invokeStatic这四条字节码指令时
2.对类进行反射调用时,该类未初始化
3.初始化一个类时,其父类若没有初始化则先初始化其父类(接口除外)
4.Main()函数所在的类
5.Jdk1.7中的动态语言的支持
以上称为对类的主动引用,被动引用不会引发初始化:
1.通过子类引用父类的静态字段
2.通过数组定义来引用类
3.引用类的常量(finalstatic的变量会在编译的时候存入常量池)
加载: 通过类的全限定名加载二进制字节流;将这个流所代表的静态存储结构转化为方法区的运行时数据结构;在方法区中生成一个代表这个类的Class对象
验证: 确保class文件中的字节流中的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全:文件格式验证、元数据验证、字节码验证、符号引用验证
准备: 为类变量分配内存并设置类变量初始零值
解析: 将常量池中的符号引用替换为直接引用。这一阶段会根据需要发生在初始化之前或之后,包含类或接口解析、字段解析、方法解析符号引用是无关虚拟机实现的内存布局。直接引用是和虚拟机实现的内存布局相关的。符号引用必须在运行期转换获得真正的内存入口地址
初始化: 执行\<clinit\>方法,该方法由编译器自动按书写顺序收集类中所有的类变量的赋值动作和静态语句块中的语句合并而成。
new一个类时:父类静态、子类静态、父类成员域、父类构造代码块、父类构造方法、子类成员域、子类构造代码块、子类构造方法
3、对象创建方法,对象的内存布局,对象的访问定位
对象的创建:
- 当虚拟机遇到一条new指令时,首先检查这个指令的参数能否在常量池中定位到这个类的符号引用,并检查这个类是否已经被加载、解析、初始化过。如果没有必须进行类加载过程
- 为新生对象分配内存,通过指针碰撞或者空闲列表法。为了解决并发问题可以采用TLAB本地线程分配缓冲策略
- 初始化为对应类型的零值
- 在虚拟机对对象进行设置之后,执行\<init\>方法按照程序员的意图初始化
对象内存布局:
对象在内存中的存储布局可以分为:对象头、实例数据、对齐填充对象头中存放:对象自身运行时数据、指向类元数据的指针
对象的访问定位:
使用句柄和直接指针两种方式
4、双亲委派模型
类加载器通过组合的方式建立的父子关系,称为双亲委派模型。
类需要由加载它的类加载器和类本身一同确立其在虚拟机中的唯一性。
BootstrapClassLoader: 负责加载JAVAHOME/lib目录下,或者被-XbootclassPath参数所指定的路径中的类库
ExtensionClassLoader: 负责加载JAVAHOME/lib/ext目录下或者被java.ext.dirs系统变量所指定的路径中的所有类库
ApplicationClassLoader: 是ClassLoader.getSystemClassLoader()方法的返回值,负责加载用户类路径上所指定的类库
工作过程:
一个类加载器收到了类加载的请求,它首先不会尝试自己加载这个类而是把这个请求委派给父加载器去完成。只有当父加载器反馈自己无法完成这个请求时,子加载器才会尝试自己去加载。
作用:
Java类随着它的类加载器一起具备了一种带有优先级的层级关系,比如Object这个类,无论哪个类加载它最终都是委派到bootstrapClassLoader去加载。保证了java程序的稳定运行。
5、静态分派与动态分派
静态分派:
依赖静态类型来定位方法执行版本的分派动作称为静态分派,典型应用是Overload。在方法接收者(执行方的所有者)已经确定的情况下,使用哪个重载版本在编译阶段就已经确定。实际类型的变化在运行期才能确定。
动态分派:
在运行期依靠实际类型决定确定发放执行版本的分派过程称为动态分派,典型的应用是Override。实现的关键是invokevirtual指令:找到引用所指向的实际类型记作C,如果在类型C中找到了与目标方法特征签名都相同的方法并通过访问权限校验直接调用,若不通过则一次查找其父类中的方法,如果始终没有找到则抛出AbstractMethodError异常
6、GC的两种判定方法
引用计数法:
在对象中添加一个引用计数器,每一个地方引用它值加1,当引用失效时值减1。很难解决对象间互相循环引用的问题。
可达性分析算法:
一个对象到GCRoots不可达就会判定为可回收对象
GCRoots包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中引用的对象。如果一个对象与GCRoots不可达时,将会被第一次标记并筛选判断是否有必要执行finalize()方法,有必要执行的进入F-QUEUE队列,稍后虚拟机将自动建立一个低优先级的线程去执行它。稍后虚拟机将进行二次标记:在finalize()方法中建立了与GCRoots的通路,仍然可以存活
7、java引用都有哪几类
强引用: 类似Obo=newOb();永远不会回收强引用引用的对象
软引用: 只有在内存不足时(将发生异常前),才会被回收。用来实现缓存
弱引用: 下一次垃圾回收就会被回收。Util包中的WeakHashMap就使用了弱引用。它的key都被封装成弱引用,一旦强引用被删除,这个弱引用就会在下一次被回收。用来解约内存空间。
虚引用: 不会对垃圾回收时间产生影响,这个对象被回收时,系统会受到一个通知。在创建虚引用的时候必须传入一个引用队列。
ReferenceQueue:我们可以声明虚引用来引用我们感兴趣的对象,在gc要回收的时候,gc收集器如果发现它还有虚引用会把这个虚引用添加到referenceQueue,这样我们如果检测到referenceQueue中有我们感兴趣的对象的时候,说明gc将要回收这个对象了。此时我们可以在gc回收之前做一些其他事情,比如记录些日志什么的。
ReferenceQueuerefQueue=newReferenceQueue();
DigitalCounterdigit=newDigitalCounter();
PhantomReference\<DigitalCounter\>phantom=newPhantomReference\<DigitalCounter\>(digit,refQueue);
8、堆里面的分区各自特点
分区:Eden、fromsurvival、tosurvival、老年代
Eden区
位于Java堆的新生代。新生代中的对象寿命较短。大多数情况下对象有现在Eden区分配,如果启动了TLAB(本地线程分配缓冲)则优先在TLAB上分配。如果Eden区内存也用完了,则会进行一次MinorGC。
Survival区
复制算法将内存分为Eden、fromsurival、tosurvival三个区域,使用的时候每次只使用Eden和fromsurvival区域,当回收时将Eden和fromsurvival区中的活着的对象复制到tosurvival区上。
老年代
里存放的都是存活时间较久的,大小较大的对象:
大量连续内存空间的大对象直接进入老年代、长期存活的对象将进入老年代。
当老年代容量满的时候,会触发一次FullGC,回收年老代和年轻代中不再被使用的对象资源。
9、常见垃圾收回算法
标记清扫法: 首先标记处所有需要回收的对象,然后统一回收,老年代
复制算法: 将内存划分为大小相等的两块,每次只使用其中的一块,当块用完了,就把还活着的对象复制到另一块上,然后把已使用的那块一次清理。新生代的收集都采用复制算法
标记整理法: 先标记需要回收的对象,然后让所有存活的对象都向一端移动,老年代
10、GC收集器有哪些?CMS收集器与G1收集器的特点?如果让你优化收集方法,有什么思路?
新生代收集器:Serial(单线程)、ParNew(多线程)
老年代:CMS(标记清扫)、SerialOld(标记整理)、ParallelOld
Jdk1.7:G1收集器(标记整理)
CMS收集器: 是一种以获取最短回收停顿时间为目标的收集器。基于标记清除算法实现,整个过程分为以下4个步骤:
(1)初始标记:仅仅只标记一下GCRoots能直接关联的对象,速度很快;
(2)并发标记:进行GCRootsTracing;
(3)重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,比初始标记时间稍长,但远比并发标记短。
(4)并发清除;耗时最长的并发标记和并发清除都可以和用户线程一起工作。
有如下缺点:
(1)CMS收集器对CPU资源非常敏感
(2)无法处理浮动垃圾,可能出现ConcurrentModeFailure失败而导致另一次的FullGC。由于CMS并发清理阶段用户线程还在运行,伴随程序运行自然就会有新的垃圾不断产生,CMS无法在当次收集中处理掉他们,只好留待下一次GC时再清理。所以需要预留一部分空间存储这部分垃圾,当预留的内存无法满足程序需求,就会出现一次ConcurrentModeFailure失败。
(3)标记清除算法将会产生大量空间碎片。
G1收集器:
(1)并行与并发:G1能充分利用多CPU、多核环境下的硬件优势
(2)分代收集:采用不同的方式处理新建对象和已存活对象
(3)空间整合:基于标记整理算法实现,不会产生空间碎片
(4)可预测停顿:G1除了追求低停顿外,还能建立可预测的时间停顿模型
G1将整个Java堆分为多个大小相等的独立区域,虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离了,它们都是一部分Region的集合。G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许收集的时间,优先回收价值最大的Region。
G1使用RememberedSet来避免全堆扫描,G1中每个Region都有一个与之对应的RememberedSet,当一个对象被引用时就会把相关引用信息记录到被引用对象所属的Region的RememberedSet之中,在进行垃圾回收时,就不会进行全堆扫描。
步骤:
(1)初始标记:仅仅只标记一下GCRoots能直接关联的对象,耗时很短
(2)并发标记:从GCRoot开始对堆中对象进行可达性分析,找出存活的对象,耗时较长,但可与用户线程并发执行。
(3)最终标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,更新RememberedSet
(4)筛选回收:对Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划
优化思路:
因为已经使用了标记-整理算法,所以应该从停顿时间上进行优化
11、MinorGC与FullGC的触发机制
MinorGC(新生代GC): 大多数情况,对象在新生代Eden区中分配,当Eden区中没有足够的空间,虚拟机触发一次MinorGC。MinorGC非常频繁、速度快。
FullGC(老年代GC)):
1、MinorGC之前会先检查老年代中最大可用连续空间是否大于新生代中的对象,如果成立,则这次MinorGC是安全的。否则就要查看是否允许担保失败,如果不允许则改为进行FullGC,允许的话就尝试进行一次MinorGC;
2、此外当老年代容量满的时候,也会触发一次fullGC
12、几种常用的内存调试工具:jmap、jstack、jconsole
1、jps: 输出JVM中运行的进程状态信息
jps[options][hostid]
如果不指定hostid就默认为当前主机或者服务器
参数说明:
-q:不输出类名、Jar名和传出main方法的参数
-m:输出传入main方法的参数
-l:输出main类和Jar全限定名
-v:输出传入JVM的参数
2、jstack: 查看某个Java进程内的线程堆栈信息
jstack[option]pid
参数说明:
-l:longlistings,会打印出额外的锁信息,在发生死锁时可以用jstack–lpid来观察锁持有情况
-m:mixedmod,不仅会输出java堆栈信息,还会输出C/C++堆栈信息(比如Native方法)
jstack可以定位到线程堆栈,根据堆栈信息可以定位到具体代码。
ps–ef或者psaux查看进程
ps–Lfppid或者top–Hppid找出该进程内最耗费cpu的线程
3、map(MemoryMap)和jhat(JavaHeapAnalysisTool): jmap用来查看堆内存使用情况,一般结合jhat使用。
jmap[option]pid
如果运行在64位JVM上,可能需要指定-J-d64命令选项参数.
4、Console: 一个内置Java性能分析器
5、statJVM: 计监测工具
13、System.gc方法有什么用
希望进行一次垃圾回收。但是它不能保证垃圾回收一定会进行,而且具体什么时候进行是取决于具体的虚拟机的,不同的虚拟机有不同的对策。
14、Object.finalize方法有什么用
1、finalize()是由JVM自动调用的,JVM感觉内存空间有限时,才会开始执行finalize()。
2、gc只能清除在堆上分配的内存,而finalize回收在栈上面分配的内存。
3、finalize()方法是对象逃脱死亡命运的最后一次机会,而且任何一个对象的finalize()方法都只会被系统调用一次。