定义泛型,类的方法出入参能不能定义泛型。***
- 接口可以定义泛型,如:T getData(); 在实现这个接口时,可以在接口后面加一个确定返回的参数类型
- 类的方法出入参也可以用泛型
类加载机制
- 类加载器
- bootstraploader 启动类加载器(引导类加载器)
- jvm启动时运行,加载jre下的主要类包,底层是c++写的
- extclassloader 扩展类加载器,加载lib/ext下的扩展类包
- applicationloader 应用程序类加载器,加载classpath下的类包
- customerClassLoader 用户自定义类加载器,加载用户自定义路径下的类包
- bootstraploader 启动类加载器(引导类加载器)
- 双亲委派
- 类加载器接到请求后会将加载任务委派给他的父类,如果其父类加载器还有父类加载器,则依次向上委派,直到最上层加载器。这样做的好处是避免类的重复加载,并可以解决一些加载的安全问题,比如用户自己写了一个java.lang.String类包,这是使用双亲委派机制是无法进行加载的,因为启动类加载器会在核心库中优先加载这个类包,不会重复加载。
- 类的加载过程
- 加载:加载类的字节码文件
- 验证:确保类符合jvm规范,没有安全问题
- 准备:为静态变量分配内存,并设置默认值
- 解析:把常量池中的符号引用转化为直接引用
- 初始化:为静态变量赋值初始值
jvm内存结构
- 程序计数器
- 线程私有,用于记录当前程序执行到哪一行
- 虚拟机栈
- 线程私有,虚拟机栈包括局部变量表、操作数栈、动态链接、方法出口的信息
- 局部变量表:用来存储基本数据类型的局部变量,和对象的引用,在编译期间完成分配。
- 操作数栈:主要用于程序计算的临时存储空间
- 线程私有,虚拟机栈包括局部变量表、操作数栈、动态链接、方法出口的信息
- 本地方法栈
- 线程私有,当java运行c/c++编写的代码时,会用到本地方法栈。
- 堆
- 堆是被所有线程共享的,主要存放new出来的对象,和数组。堆空间分为,年轻代,老年代,年轻代又分为伊甸园和幸存区,幸存区又分为(from、to)。堆空间可以被垃圾回收器回收内存。
- 方法区(1.7永久代,1.8元空间)
- 方法区是所有线程共享的,用来存储常量,静态变量,类信息,运行时常量池
- 常量池的优点,常量池可以避免频繁创建/摧毁对象带来的性能问题,实现了对象的共享
- integer-128--127在常量池的integercache中用的时候直接取。
- 一文搞懂JVM内存结构_涛声依旧叭的博客-优快云博客_jvm内存结构
String str = "abc" 和 String str = new String("abc");的区别
- String str = "abc" 可能会创建 0或1个对象
- 当执行语句时,会判断常量池中是否存在abc,如果存在则直接引用abc的地址,此时创建了0个对象。
- 当执行语句时,如果判断常量池中没有abc,则会先在池中创建abc,然后再引用abc的地址,此时创建了1个对象。
- String str = new String("abc"); 可能会创建1或2个对象
- 当执行语句时,会判断池中是否存在abc,如果存在,则只在堆内存中创建abc对象,并引用池中的地址,此时创建了1个对象。
- 如果判断池中没有存在abc,则在池中创建abc,然后再在堆中创建abc对象,引用池中的地址,此时创建了两个对象。
- 有上述可证,new出来的string用==比较会返回false,因为引用的地址都是新生成的。
什么样的java对象会被垃圾回收器回收
- 通过可达性算法分析
- 通过GC Roots向下搜索,搜索完连接起来的路径成为引用连,如果一个对象到GC Root是没有任何引用连时,则证明此对象不可达,会被判断为可回收对象。
- 什么样的对象可以作为GC Roots
- 虚拟机栈中引用的对象,如:局部变量,临时变量等。
- 方法区中静态属性引用的对象。
- 方法区中常量引用的对象,比如:字符串常量池里的引用。
- 本地方法栈中Native引用的对象
- 引用
- 强引用,设置成null
- 软引用,当堆内存满了会被回收
- 弱引用,会在GC的时候被回收
- 虚引用,是通知jvm对直接内存进行回收
new 一个java对象的过程
jvm垃圾回收算法
- 标记-清除:遍历整个内存区域判断对象是否可达,进行标记操作,如果不可达则将其标记,然后再次遍历清除被标记的对象回收内存。缺点是要循环两遍,效率太低,而且清理完内存不连续。
- 标记-复制:将内存区域划分为相同大小的两块,每次只用一块内存区域进行存储,然后一次清理掉没有没有的内存,并将其移动到另一块没用的内存区域,并把使用的内存连续存储,反复如此。缺点是内存利用率不高,浪费了一半的内存。
- 标记-整理:对标记的内存进行回收,然后将存活的对象对内向前移动聚集在一起。
- 分代算法:
伊甸园eden,最初对象都分配到这里,与幸存者区servivor(分为from、to)合成为新生代
当伊甸园的内存不足时,标记伊甸园与from的存活对象(现阶段没有)
将存活对象采用复制算法复制到to中,复制完毕后,伊甸园和from内存都得到释放
将from和to交换位置
标记伊甸园和from的存活对象
将存活对象复制到to中
复制完毕后,伊甸园和from的内存都得到释放
将from和to交换位置
老年代old,当幸存区对象熬过几次回收(最多15次,MaxTenuringThreshold参数可以设置晋升年龄),晋升到老年代。
进入老年代的四种情况:
- 对象存活年龄到达阈值(MaxTenuringThreshold参数可以设置晋升年龄)(年龄分代)
- 大对象直接进入老年代(使用参数PreTenureSizeThreshold可以设置对象的大小)
- 幸存者区中如果有相同年龄的对象占用的总空间大于幸存者区的一半,那么其他年龄大于该年龄的对象可以直接进入老年代。(动态年龄判定)
- MGC后,幸存者区不能容纳全部存活对象,直接进入老年代。(担保机制)
触发Minor GC条件
- 当Eden区满时,出发Minor GC
触发Full GC条件
- 调用System.gc时,系统建议执行Full GC,但是不是必然会执行
- 老年代空间不足
- 方法区空间不足
- 通过Minor GC后进入老年代的对象大小,大于老年代的可用内存。
- 由Eden区和S form区向S to区复制时,对象大小大于S to可用内存时,则把该对象存入老年代,且老年代的内存小于该对象。
常见垃圾回收器
- serial 单线程新生代垃圾回收器,采用复制算法
- serial old 单线程老年代垃圾回收器,采用标记整理算法
- parNew 是serial的多线程实现,采用复制算法
- paraller scavenge 高效的多线程垃圾回收器,采用复制算法
- paraller old 老年代多线程垃圾回收器,采用标记整理算法
- CMS垃圾回收器,采用多线程标记清除算法
- G1垃圾回收器,是一种高吞吐量的垃圾回收期,始于jdk1.7版本,jdk1.9默认的垃圾回收期为G1
垃圾回收器的选择跟内存大小的关系
- serial 几十兆
- paraller scavenge 上百兆 -- 几G
- CMS 20G 低于10GCMS比G1效果好
- G1 上百G
- ZGC 4T -- 16T(JDK13)
JDK默认垃圾回收器
- jdk1.7 Parallel Scavenge + Paraller Old
- 设置参数 -XX:+UseParallelGC -XX:+UseParallelOldGC
- jdk1.8 Parallel Scavenge + Paraller Old
- 设置参数 -XX:+UseParallelGC -XX:+UseParallelOldGC
- jdk1.9 G1 设置参数
- 设置参数 -XX:+UseG1GC
CMS垃圾回收器(CMS只会回收老年代和永久代)
CMS垃圾回收器采用三色标记算法实现的垃圾回收大致分为4个步骤:
- 初始标记:暂停所有线程(STW stop the world),仅找到所有根节点GCRoots,所以即使STW速度也很快。
- 并发标记:这个阶段中gc线程和业务线程是同时运行的,gc线程会找到根节点下的所有引用,但是不需要STW。因为这一步是gc线程与业务线程并行执行,所以这个过程中可能会出现以下两种情况:a.对象从可达变为不可达,b.对象从不可达变为可达。
- 通过三色标记算法,会将已经遍历到的并且遍历完成它的所有子引用的对象标记为黑色,把已经遍历到但是没有遍历其子引用的标记为灰色,把没有遍历到的标记为白色。这里会有一个bug,当一个对象A标记为黑色后,cpu时间片分给了业务线程,此时对象A引用了对象B,这时因为A的颜色为黑色,所以B是会被回收的,CMS对这个bug进行了修复,当标记为黑色的对象引用了新的对象时,会把黑色变为灰色,在gc线程下一次扫描的时候会继续扫描它的子引用。bug2:如果A对象中有两个属性1和属性2,gc线程第一次扫描的时候,发现属性1有引用,会把A对象标记为灰色,cpu时间片分给了业务线程,通过业务操作,属性1又引用了对象B,cpu继续处理gc线程,由于属性1已经遍历完成,继续遍历属性2,遍历完成后把A对象标记为黑色,此时会出现一个bug,B对象虽然被引用但是并没有被标记颜色,所以会有CMS垃圾回收器的第三步骤:重新标记。
- 重新标记:将并发标记过程生成的引用结构重新遍历一遍,解决并发标记过程中会出现的对象从不可达变为可达的问题,这个阶段需要STW。为什么不对另一种情况进行重新标记,因为需要对引用链上的对象进行分析,会比较耗时。
- 并发清除:这个阶段也是gc线程与业务线程并行执行,如果在当前阶段有对象被引用,则直接标记为黑色,然后清理所有黑色以外的对象。在这个阶段中如果引用了新的对象,那么会直接把这个对象标记为黑色,等到下一次垃圾回收的时候再重新遍历。
G1
- G1垃圾回收器依然采用三色标记算法,在处理cms中并发标记的bug时,它会把灰色引用白色的指针记录下来存入堆栈中,当gc扫描的时候发现堆栈中有指针,则去扫描被舍弃的对象是否有其他引用,如果有则无需回收,如果没有则当垃圾处理。
- G1把堆分成若干region,每个region既可以存放新生代eden、survive也可以存放老年代,如果存放大对象时一个region容量不过,可以划分多个region用于存储大对象。同时相比以往的垃圾回收器,G1垃圾回收器可以同时回收新生代的垃圾数据又可回收老年代的垃圾数据,如果出现跨region引用,会在被应用的头对象空间中存储引用对象的地址,然后再垃圾收回时,判断引用地址对象是否存活,如果没有存活则可以进行回收。
- G1中的YoungGC,YoungGC并不是eden区满了就立即触发的,而是G1要计算一下现在eden要回收所需要的时间,如果时间远远小于 -XX: MaxGCPauseMillis,那么会增加年轻代的eden,然后等到下一次触发YoungGC时,如果回收所需时间跟-XX: MaxGCPauseMillis 很接近,才会进行YoungGC。
- G1中的MixedGC(混合GC),老年代的占有率超出设定的值时触发,回收所有Young区、Old区、大对象区,MixedGC主要使用复制算法,把存活的对象考到其他的region中,如果region空间不够,则会触发FullGC。
- FullGC会停止系统程序,然后采用单线程进行标记、清除和压缩整理,空闲出的region供MixedGC使用。
- 参数设置:
- -XX:G1HeapRegionSize Region的大小
- -XX:MIN_Region_SIZE 最小region的大小
- -XX:MAX_Region_SIZE 最大region的大小
- TARGER_Region_number region的数量
jvm调优
jps :列出所有java进程
jinfo :进入jvm进程信息 pid为jsp查询出的进程id
jstat :查看jvm使用情况
top:查看linux CUP使用情况
jstack :查看jvm中的线程情况,及线程中的调用情况
-- GC常用参数
- -Xmn: 年轻代大小 -Xms: 最小堆 -Xmx: 最大堆 -Xss: 栈空间
- -XX:+UseTLAB 使用TLAB,默认打开
- -XX:TLABSize 设置TLAB大小
- -XX:+PrintTLAB 打印TLAB的使用情况
- -XX:+DisableExplictGC 让代码中的System.gc()失效
- -XX:+PrintGC 打印GC情况
- -XX:+PrintGCDetails 打印GC的详细使用情况
- -XX:+PrintHeapAtGC GC的时候打印一个堆栈的情况
- -verbose:class 类加载详细过程
- -XX:+PrintVMOptions 打印虚拟机运行时参数
- -XX:+PrintFlagsFinal -XX:+printFlagsInitial 必须会用,用于查找配置命令,
- 如:java -XX:+PrintFlagsFinal -version | grep G1
- -Xloggc:opt/log/gc.log 打印GC日志
- -XX:preBlockSpin 锁自旋次数
Paraller 常用参数
- -XX:SurvivorRatio 设置新生代Eden去所占空间比例,如果-Xmn:100m -XX:SurvivorRatio:8,eden分80M,survivor分20M(s0分10M,s1分10M)
- -XX:MaxTenuringThreshold 设置晋升老年代的年龄,最大值15
- -XX:PreTenureSizeThreshold 设置直接分配老年代大对象的大小
- -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设置为何CPU核数相同
- -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例(hotspot自己分配)
CMS常用参数
- -XX:+UseConcMarkSweepGC 启动命令
- -XX:ParallelCMSThreads CMS线程数量
- -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代开始CMS回收,默认是68%(近似值),如果发生频繁SerialOld卡顿,应该调小(频繁CMS回收)
- -XX:+UseCMSScompactAtFullCollention 在FGC时进行压缩(打开后在进行CMS时进行压缩)
- -XX:CMSFullGCsBeforeCompaction 多少次FGC之后进行压缩
- -XX:+CMSClassUploadingEnabled 回收永久代,MateSpace
- -XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行Perm回收
- GCTImeRatio 设置GC时间占用程序运行时间的百分比
- -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代
G1常用参数
- -XX:+UseG1GC 使用G1垃圾回收器
- -XX:MaxGCPauseMillis young区回收处理时间建议值,G1会尝试调整Young区的块数来达到这个值
- -XX:GCPauseIntervalMillis 设置GC的间隔时间
- -XX:+G1HeapRegionSize region的大小,建议逐渐增大该值,随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会变长。
- -XX:G1NewSizePercent 新生代最小比例,默认为5%
- -XX:G1MaxNewSizePercent 新生代最大比例,默认为60%
- -XX:G1TimeRatio GC时间建议比例,G1会根据这个值调整堆空间
- ConcGCThreads 设置并发线程数量
G1混合回收mixed
- -XX:InitiatingHeapOccupancyPercent,默认值是45,也就是老年代占据了堆内存45%的Region的时候启用MixedGC
- -XX:G1MixedGCCountTarget,默认为8,这个参数表示最后的混合回收阶段会执行8次,一次只回收掉一部分的Region,然后系统继续运行,过了一小段时间后,又再次进行混合回收,重复8次。执行这种间断的混合回收,就可以把每次的混合回收时间控制在我们需要的停顿时间之内了,同时达到垃圾清理的效果。
- -XX:G1HeapWastePercent 默认为5,他的意思是在混合回收的时候,Region都是基于复制算法去垃圾回收的,将一个Region内的存活对象放入另一个Region中,再把这个Region的垃圾对象给清理掉,这样就不会产生内存碎片了。清理掉垃圾对象的Region会不断的口空闲出来,一旦达到了堆内存的5%,就会停止该次的混合回收
- -XX:G1MixedGCLiveThresholdPrecent 默认值为85,意思是如果有一个Region中的存活对象大于Region大小的85%的话,就不去回收这个Region,否则回收时将85%的存活对象放入另一个Region中,得不偿失。
排除OOM
- 系统已经OOM,可以提前配置 -XX:+HeapDumpOutOfMemoryError -XX:HeapDumpPath= 当出现OOM时可以生成dump文件。
- 系统运行中还未OOM,导出dump文件:jmap -dump:format=b,file=fileName.hprof pid(如14660)。
- 基于visualVM进行分析定位问题,查看系统使用率最高跟业务相关的对象,找到GCRoot,查看线程栈,定位问题。
- 使用图形界面监控工具直连,一定要在压测环境下, 否则会对生产环境造成压力,并且使用图形监测工具连接linux需要开放大量接口,运维那关过不去。
- 如果要导出堆转储(dump)文件,最好先从负载中摘除一台机器,然后再导出文件,避免导出过程中,请求打进来造成阻塞超时等问题。
- 使用TcpCopy把请求复制到测试环境进行jmap监测。
- 使用arthas
- java -jar arthas-boot.jar 启动 ,启动会自动查找服务器中的java进程
- 输入help 查看所有命令
- dashboard 仪表盘,可以查看那个线程在吃内存,年轻代 老年代的使用情况,和运行时环境
- heapdump 替代 jmap
- thread 查看所有线程
- thread -b 查看死锁
- jvm 查看jvm设置的一些参数
- jad 1
- watch
- trace 可以查看一个方法中调用的其他方法的耗时
JVM方法调用执行流程
- 判断是否存在编译版本(是否有代码缓存)
- 如果有则直接执行编译后的本地代码版本
- 如果没有先进行调用计数器值+1
- 判断本地累加的值与原来存的值之和,是否超过阈值
- 如果超过了阈值,向编译器提交编译请求,这一步操作类似异步操作,本次方法的返回仍然使用解释器执行方法。
- 如果没有超出阈值使用解释器方法执行
- 最终方法返回。
方法或循环体调用次数超过10000次则会进行编译,阈值通过参数-XX:CompileThreshold设置。
解释器与编译器各自的优点:
解释器不占用内存,如果jvm内存有限制可以通过解释器执行的方式减少内存的开销。
编译器可以在多次对相同方法的调用上提高效率,减少解释器的重复工作。
一个java对象多大
对象内存布局,new 出来的对象 如 new T(); (一个对象多大)
- markword 8个字节 8*8=64位 //64位系统8个字节 8*8=64,32为系统4个字节 4*8=32
- class pointer 指针指向T.class 默认是4个字节 //如果开启压缩是4个字节,jvm默认开启压缩,如果不压缩则是8个字节
- 成员变量所占字节 //开始压缩后 如 string object对象为4个字节,关闭压缩为8个字节
- padding 用来被8整除对其, 如果是64位虚拟机,整个对象内存必须能被8整除,padding的作用是要补齐内存,如果前三项占用内存12k,那么padding就是4k
逃逸分析
- 当创建一个对象时,这个对象在方法的外部可以被使用,这种情况称为对象逃逸,此时把此类对象的存储分配到堆空间。当一个对象只在方法内部使用,外部无法获取时,jvm会将此类对象存储到栈内存中,让其在方法结束时跟随栈内存一起被回收掉。jdk1.7版本后默认开始逃逸分析。参数为-XX:+DoEscapeAnalysis开启,关闭把加号换成减号-XX:-DoEscapeAnalysis。0
- 当通过逃逸分析确定该对象不会被外部访问,对象会被存储到栈内存,当栈内存空间没有连续空间可以存储时,会把对象的成员变量拆分出来进行代替存储到栈内存空间,这种逻辑成为标量替换。配置参数:-XX:+EliminateAllocations,jdk1.7之后默认开启。
- 标量替换的前提是要开启逃逸分析,使用他们的好处是可以减少堆内存gc。
什么样的类能被回收
需要同时满足一下三点:
- 该类的所有对象实例都已经被回收,也就是Java堆中不存在的该类的任何实例。
- 加载该类的ClassLoader已经被回收。ClassLoader也是一个对象所以可以被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
你提到的jvm的动态链接是怎么回事,他的作用
yonggc频繁怎么处理
实际项目中jvm配置的内存大小时多少
你在项目中是怎么进行jvm调优的
- jVisualVM
- jconsloe
- Arthas
- 在arthas的命令行界面,输入dashboard命令,会实时展示当前程序线程状态、JVM各区域、GC情况等信息
- 输入thread命令,会显示所有线程的状态信息
- 输入thread -b,找到阻塞其他线程的线程
- 输入jvm命令,查看jvm详细的性能数据
怎么检测某一个线程占用的内存多
CPU100%排查
- 先使用top命令 查看CPU使用情况,可以查看到具体是哪个进程占用CPU
- 然后执行 ps H -eo pid,tid,%cpu | grep 查看执行进程下哪个线程占用CPU
- 最后使用 jstack 可以查看进程下线程的执行情况,然后线程id转成16进制去跟nid(16进制)匹配,然后可以看到当前线程正在执行哪块代码,这时候去排查代码即可