垃圾回收机制,不可达算法
垃圾回收就是对内存堆中已经死亡和长时间没使用的对象进行清除和回收,释放垃圾占用的空间,防止内存泄露,有效的使用内存,先使用垃圾判断算法(引用计数法、可达性分析算法)判断哪些对象是垃圾对象,再使用垃圾回收算法(标记-清除算法 、复制算法 、标记-整理算法 、分代收集算法)对垃圾对象进行回收释放;
垃圾判断算法:
引用计数法:在对象上添加一个引用计数器,每当有一个对象引用它时,计数器加1,当使用完该对象时,计数器减1,计数器值为0的对象表示不可能再被使用。引用计数法实现简单,判定高效,但不能解决对象之间循环引用的问题。
可达性分析法:过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,当一个对象到 GC Roots 没有任何引用链时,意味着该对象可以被回收。以下对象可作为GC Roots:本地变量表中引用的对象(局部变量)、方法区中静态变量引用的对象(static成员变量)、方法区中常量引用的对象(static final常量)、Native方法引用的对象;
垃圾回收算法:
标记-清除算法:对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收;缺点:由于直接回收不存活对象,会造成内存碎片;
标记-整理算法:基于标记-清除算法,清除后移动存活的对象,可解决内存碎片,年老代默认使用;
复制算法:将内存分成大小相等的两块区域,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块区域上,然后对该块进行内存回收,年轻代默认使用;
分代收集算法:是对上面三种算法的应用,分代使用不同算法,年轻代使用复制算法,老年代使用标记-整理算法;
垃圾收集器:
Serial/SerialOld:单线程GC收集器包括Serial和SerialOld这两款收集器,分别用于年轻代和老年代的垃圾收集工作。后来,随着CPU多核的普及,为了更好了利用多核的优势,开发了ParNew收集器,这款收集器是Serial收集器的多线程版本。
Parallel Scavenge/ParNew/ParallelOld:多线程收集器还包括Parallel Scavenge、ParNew和ParallelOld收集器,这两款也分别用于年轻代和老年代的垃圾收集工作,不同的是,它们是两款可以利用多核优势的多线程收集器。
CMS:相对来说更加复杂的还有CMS收集器。这款收集器,在运行的时候会分多个阶段进行垃圾收集,而且在一些阶段是可以和应用线程并行运行的,提高了这款收集器的收集效率。
G1:其中最先进的收集器,要数G1这款收集器了。这款收集器是当前最新发布的收集器,是一款面向服务端垃圾收集器。
年轻代收集器:Serial收集器、ParNew收集器以及Parallel Scavenge收集器。老年代收集:Serial Old收集器、Parallel Old收集器以及CMS收集器。 JDK7/8默认: UseParallelGC即Parallel Scavenge+Parallel Old,JDK9默认G1;
JVM的一些命令
jps:通过jps 命令查看 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息; jinfo:通过jinfo命令查看JVM配置信息,包括命令行参数、系统变量,也可以用其来修改命令行参数; jstack:显示虚拟机的线程栈信息,用于生成当前JVM的所有线程快照;jstat:实时显示本地或远程JVM进程中类装载、内存、垃圾收集、JIT编译等数据; jmap:用于生成虚拟机的内存快照信息; jconsole:内置 Java 性能分析器 ;jcmd :一个多功能的工具,可以用它来导出堆、查看Java进程、导出线程信息、执行GC、还可以进行采样分析(jmc 工具的飞行记录器) ;jvisualvm :可视化性能诊断工具;jmc:任务控制工具,提供Java 监控和管理,JMC 与 Java Flight Recorder 一起工作,用来记录核心数据和事件;
JVM内存模型
JVM将内存划分为6个部分:
PC寄存器(也叫程序计数器):记录当前线程运行位置,每个线程都有一个独立的程序计数器,线程的阻塞、回复、挂起等操作都需要程序计数器的参与,因此是线程私有的。
虚拟机栈:创建线程时创建,用来存储栈帧,因此也是线程私有。java程序中的方法执行时,会创建一个栈帧,用于存储临时数据 、中间结果、局部变量表、操作数栈、动态链接、方法出口等信息。溢出会报StackOverflowError。
本地方法栈:支持native方法,如在java中调用C/C++。
堆:所有线程共享,用于存储对象。堆空间不够,同时无法申请足够内存时,会报OutOfMemoryError。
方法区(也叫永久代):各个线程共享,存储静态变量、运行时常量池等信息。
运行时常量池:是方法区的一部分,每个类一个,从class常量池中迁移过来,程序中使用的常量值。
从JDK8开始,没有方法区,原方法区中的一部分数据(符号引用、静态变量、字面量如字符串)转移到堆中,一部分数据(类元数据)转移到元空间,元空间属于本地内存/直接内存/堆外内存,不属于虚拟机管理,减少了垃圾回收、提升了IO效率(堆外属于内核态,堆内属于用户态,NIO减少了数据复制),内存释放可通过代码显示直接释放或者通过GC间接释放;
如何把Java内存的数据全部dump出来
方式一:获取内存详情:jmap -dump:format=b,file=e.bin pid;方式二:获取内存dump: jmap -histo:live pid, 这种方式会先出发fullgc,所有如果不希望触发fullgc 可以使用jmap -histo pid;方式三:jdk启动加参数: -XX:+HeapDumpBeforeFullGC ; -XX:+HeapDumpOnOutOfMemoryError; -XX:HeapDumpPath;
如何手动触发全量回收垃圾,如何立即触发垃圾回收
System.gc() 或者Runtime.getRuntime().gc() 并且调用runtime.runFinalizationSync() 设置标识位为true;或者执行jmap命令可出发GC;或者使用jconsole上的按钮可以触发GC;
何时会内存泄漏,内存泄漏会抛哪些异常
连接没正确释放,如数据库连接、网络连接、IO连接; 静态集合类引起内存泄漏, 静态变量的生命周期和应用程序一致,他们所引用的对象不能释放;释放对象时没删除对象的监听器;单例对象不正确使用, 单例对象存在于JVM整个生命周期,若引用了外部对象,容易内存泄露;
StackOverflowError( 线程请求的栈深度大于虚拟机所允许的最大深度 )、OutOfMemoryError(Java Heap Space、PermGen Space、Metaspace);
静态内部类加载到了哪个区?
静态内部类的加载类似于普通类,不依赖于外部类的加载,只有当使用静态内部类时才加载,只使用静态变量或外部类时不加载,但加载静态内部类会先加载外部类;类元数据被加载到方法区或元空间,静态变量和类实例加载到堆中;
class文件编译后加载到了哪
class文件加载过程:加载:根据全限定名加载为二进制字节流,方法区生成静态数据结构,堆中生成class对象;验证:文件格式、元数据、字节码、符号引用的校验;准备:静态变量分配堆空间和初始化数据类型的默认值;解析:符号引用转换为直接引用;初始化:执行类构造器;
虚拟机会对class文件按需加载 ,类加载时机:new、子类引用父类会先加载父类、反射调用、使用静态变量或调用静态方法、标注为虚拟机的启动类main;
sting s=new string("abc")分别在堆栈上新建了哪些对象
堆中创建字符串abc,不管存不存在都会创建,栈中新建s对象指向堆中的abc字符串;
GC分哪两种;什么时候会触发Full GC?
Minor GC/Young GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存; Major GC/Full GC:一般指对老年代GC(整个堆GC);由于出现Full GC的时候经常伴随至少一次的Minor GC,而许多 Major GC 是由 Minor GC 触发的,所以不用太区分;
Full GC触发时机:1. 调用System.gc时,系统建议执行Full GC,但是不必然执行;2. 老年代空间不足;3. 方法区空间不足;4. 通过Minor GC后进入老年代(超过年龄)的平均大小大于老年代的可用内存;5. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小;
JVM里的有几种classloader,为什么会有多种?
BootStrap根类加载器,负责加载/lib或被-Xbootclasspath指定路径下的类库,开发者不可以直接使用;Extension扩展类加载器:负责加载/lib/ext或被java.ext.dirs系统变量指定的路径中的所有类库,开发者可以直接使用;System系统类加载器,加载classpath下所有jar文件;App用户自定义类加载器;多种类加载器时处于安全考虑;
什么是双亲委派机制?介绍一些运作过程,双亲委派模型的好处
双亲委派机制: 如果一个类加载器收到了类加载请求,会先把请求委托给父类加载器,如果父类还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类无法完成加载才由子类加载器完成;好处: 具备了一种带有优先级的层次关系, 避免类的重复加载;安全考虑,防止核心类被随意替换;
什么情况下我们需要破坏双亲委派模型
双亲委派是JDK1.2引入的,ClassLoader是JDK1.0就有的,所以对于之前实现的类加载器会被破坏;
对于一些基础类要调用用户类的情况如JNDI、JDBC等,会破坏双亲委派,采用了线程上下文类加载器,可以通过setContextClassLoader设置,默认是系统类加载器,这样JNDI(位于rt.jar被根加载器加载)就存在根加载器加载子加载器的情况;
程序动态性的实现如OSGI是基于自定义加载器实现的,OSGi的类加载器时一种树状结构,破坏了双亲委派;
常见的JVM调优方法有哪些?可以具体到调整哪个参数,调成什么值?
设置内存大小(最大内存、各个代的内存比例等配置):-Xms和-Xmx设置成一样,避免每次GC后重新分配内存;-Xmn新生代大小,建议堆的3/8;-Xss线程堆栈大小; -XX:NewRatio新生代与老年代比值; -XX:SurvivorRatio伊甸区和幸存区比值; -XX:MaxPermSize持久代大小;
设置GC(主要是选择垃圾收集器,设置并行收集,收集器相关参数等配置) :-XX:+UseParallelGC/ -XX:+UseParNewGC年轻代使用并行垃圾收集器; -XX:ParallelGCThreads并行收集器线程数; -XX:+UseParallelOldGC老年代使用并行收集器; -XX:PrintHeapAtGC打印GC前后的堆栈信息; -XX:+UseAdaptiveSizePolicy收集器自动调整年轻代大小和幸存区比例; -XX:MaxGCPauseMillis=100设置年轻代每次GC时间,若达不到自动调整年轻代大小; -XX:+UseConcMarkSweepGC老年代使用CMS收集器; -XX:MaxTenuringThreshold=0垃圾最大年龄,0表示对象不经过幸存区直接进老年代,对老年代较大时避免了多次复制,设置较大时可在新生代多存活;
其他设置: -XX:ErrorFile错误日志;-XX:-HeapDumpOnOutOfMemoryError内存溢出时导出dump文件; -XX:-PrintGCDetails每次GC打印详情; -XX:OnError发生Error时运行自定义命令; -XX:OnOutOfMemoryError内存溢出时执行自定义命令; -server以服务器模式运行,64位默认,32位设置; -XX:CompileThreshold函数调用超过阈值则使用JIT编译成机器码优化。64位默认10000;
什么情况下会发生栈内存溢出
栈是线程私有的,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息,局部变量表又包含基本数据类型,对象引用类型, 所以栈溢出就是方法执行时创建的栈帧超过了栈的深度,比如方法循环或递归调用;
JVM内存为什么要分成新生代,老年代,持久代?新生代中为什么要分为Eden和Survivor?
新生代,老年代,持久代是用于分代,分代后不同代可使用不同的垃圾收集策略从而优化GC性能;如果只有Eden区,则每次MinorGC后存活的对象都被转移到老年代,老年代很快会被填满,从而增加了FullGC的次数; 所以Survivor的存在意义就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代;设置2个Survivor区时为了解决内存碎片,内存碎片化会影响性能;
JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代?
GC流程:1. 创建新对象时判断伊甸园区是否有足够的内存空间,有则直接创建,没有则进行MinorGC;2. 再判断伊甸区是否充足,有则直接创建,没有则判断幸存区是否足够;3. 幸存区足够则把伊甸区部分存活对象转移到幸存区,再在伊甸区创建对象,如果不够则判断老年代是否足够;4. 如果老年区空间充足,则将存活区中的活跃对象保存到老年区,而后存活区出现空余空间,随后伊甸园区将部分活跃对象保存地存活区中,最后在伊甸园区为新对象分配内存空间;5、如果这个时候老年代内存空间也满了,那么这个时候将产生Major GC(Full GC),然后再将存活区中的活跃对象保存到老年区,从而腾出空间,然后再将伊甸园区的部分活跃对象保存到存活区,最后在伊甸园区为新对象分配内存空间; 6. 如果老年代执行Full GC之后依然空间依然不足,则报OOM异常;对象最终都在伊甸区创建;
对象进入老年代的常见的4个时机:1. 躲过15次gc,达到15次高龄之后进入老年代;2. 动态年龄判定规则,如果Survivor区域内年龄1+年龄2+年龄3+年龄n的对象总和大于Survivor区的50%,此时年龄n以上的对象会进入老年代,不一定要达到15岁;3.如果一次Young GC后存活对象太多无法进入Survivor区,此时直接进入老年代;4.大对象直接进入老年代;
你知道哪几种垃圾收集器,各自的优缺点,重点讲下CMS和G1,包括原理,流程,优缺点
Serial收集器:单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。ParNew收集器:Serial收集器的多线程版本,也需要stop the world,复制算法。Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。 如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。Serial Old收集器:是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。CMS(Concurrent Mark Sweep) 收集器:是一种以获得最短回收停顿时间为目标的收集器, 标记清除算法,运作过程:初始标记(会Stop),并发标记(不会Stop),重新标记(会Stop),并发清除 (不会Stop) ,收集结束会产生大量空间碎片。G1收集器: 标记整理算法实现, 运作流程主要包括以下:初始标记 (会Stop) ,并发标记,最终标记 (会Stop) ,筛选清理 (会Stop) 。 不会产生空间碎片,可以精确地控制停顿;
CMS收集器和G1收集器的区别:CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;CMS收集器以最小的停顿时间为目标的收集器;G1收集器可预测垃圾回收的停顿时间;CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片;
当出现了内存溢出,你怎么排错
查看日志,判断是什么类型的内存溢出(堆、老年代、栈),使用JDK自带工具jvisualvm查看系统堆栈日志,结合日志和代码分析,如果是栈溢出排查是否有循环或递归调用,堆溢出排查大对象;
JVM内存模型的重排序,内存屏障,happen-before,主内存,工作内存等
重排序:JVM虚拟机允许在不影响代码最终结果的情况下,可以乱序执行;
内存屏障:可以阻挡编译器的优化,也可以阻挡处理器的优化;
happens-before原则:1:一个线程的A操作总是在B之前,那多线程的A操作肯定实在B之前。 2:monitor 再加锁的情况下,持有锁的肯定先执行。 3:volatile修饰的情况下,写先于读发生 4:线程启动在一起之前 strat 5:线程死亡在一切之后 end 6:线程操作在一切线程中断之前 7:一个对象构造函数的结束都该对象的finalizer的开始之前 8:传递性,如果A肯定在B之前,B肯定在C之前,那A肯定是在C之前。
主内存:所有线程共享的内存空间;工作内存:每个线程特有的内存空间;
吞吐量优先和响应优先的垃圾收集器选择
吞吐量优先的并行收集器: -XX:+UseParallelGC -XX:ParallelGCThreads=8选择Parallel Scavenge收集器,然后配置多少个线程进行回收,最好与处理器数目相等; -XX:+UseParallelOldGC配置老年代使用Parallel Old; -XX:MaxGCPauseMills=100 设置每次年轻代垃圾回收的最长时间,如何不能满足,那么就会调整年轻代大小,满足这个设置; -XX:+UseAdaptiveSizePolicy 并行收集器会自动选择年轻代区大小和Survivor区的比例;
响应时间优先的并发收集器: -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 设置老年代的收集器是CMS,年轻代是ParNew; -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection 设置运行多少次GC后对内存空间进行压缩整理,同时打开对年老代的压缩;
Class文件结构是如何解析的
class文件以8字节为基本单位来进行存储,只有两种数据类型:无符号数、表; 无符号数属于基本数据类型,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节和8个字节的无符号数; 表是由多个无符号数或者其它表作为数据项构成的符合数据类型;
class文件由16种数据项组成: 3个描述文件属性的数据项:魔数和主次版本号;11个描述类属性的数据项:类、字段、方法等信息;2个描述代码属性; 一个class文件从上到下依次为魔数(前4字节,固定为CAFEBABE)、次版本号(5,6字节)、主版本号(7,8字节)、常量池(长度不定)、访问标识(2字节)、类继承关系、字段表、方法表、属性表;
网友评论