赞
踩
目录
1.1内存结构
- 堆(Heap):线程共享。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。
- 方法区(Method Area):线程共享。存储类信息、常量、静态变量、即时编译器编译后的代码。
- 方法栈(JVM Stack):线程私有。存储局部变量表、操作栈、动态链接、方法出口,对象指针。
- 本地方法栈(Native Method Stack):线程私有。为虚拟机使用到的Native 方法服务。如Java使用c或者c++编写的接口服务时,代码在此区运行。
- 程序计数器(Program Counter Register):线程私有。有些文章也翻译成PC寄存器(PC Register),同一个东西。它可以看作是当前线程所执行的字节码的行号指示器。指向下一条要执行的指令
1.2五个分区的功能:
- 堆:
- GC的主要操作区域
- -Xms设置堆的最小空间大小。-Xmx设置堆的最大空间大小。-XX:NewSize设置新生代最小空间大小。-XX:MaxNewSize设置新生代最小空间大小。
- 存放实例和数组,结构上分为新生代和老年代,新生代又分为Eden区和两个survivor区,注意两个区没有先后关系,可以从s0传递到s1也可以从s1传递到s0,两个区总有一个是空的。
- 一般情况下,新生代占1/3,老年代占2/3。然后伊甸园区占8/10,幸存区占2/10,from和to区各占1/2。
- 堆是线程共享的
- 方法区:
- 是线程共享的区域,和heap一样,又称永久代Permanent Generation
- 它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- -XX:PermSize 设置最小空间 -XX:MaxPermSize 设置最大空间。
- 虚拟机栈:
- 每一个线程都私有的栈,存放线程中方法调用的栈帧,存放各种数据类型、对象引用等信息
- -Xss控制每个线程栈的大小。
- - StackOverflowError: 异常线程请求的栈深度大于虚拟机所允许的深度时抛出;
- - OutOfMemoryError 异常: 虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出。
- 本地方法栈:
- 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
- 与jvm栈一样,抛出的异常是一样的,-Xss配置也是一样的
- 程序计数器:
- 每一个线程都私有的,相当于一个为了控制当前线程所执行的字节码的行动指示器
- 是jvm中唯一一个没有OOM的区域
- Exception in thread “main”: java.lang.OutOfMemoryError: Java heap space
- Exception in thread “main”: java.lang.OutOfMemoryError: PermGen space
- Exception in thread “main”: java.lang.OutOfMemoryError: <reason> <stack trace>(Native method)
- Exception in thread “main”: java.lang.OutOfMemoryError: Requested array size exceeds VM limit
JMM控制java线程间通讯规则,是一个虚拟的概念,他规定了线程之间的共享变量存在主存中,每一个线程都是有一个私有的本地内存来存储主存中的数据副本,每次对主存数据操作都是先复制到本地内存中修改再返回提交给主内存。
- 概念:
.java文件存放了代码逻辑文件,被编译后形成.class字节码文件,字节码文件存放着JVM所能理解的底层指令,当需要调用某个类时就会加载它的class文件创建class对象,将字节码文件加载到jvm内存,这个过程就是类加载
- 过程:
- 加载:通过类的完全限定查找此类字节码文件,创建class对象
- 验证:确保class文件是合法的,包括文件格式、元数据、字节码、符号引用四方面的验证
- 准备:既初始化工作,为类变量分配内存和赋初值,一般为0或者null;注意final修饰的static变量是编译阶段就分配了,不在这一阶段初始化;,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
- 解析:主要将常量池中的符号引用替换为直接引用的过程。(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入Java虚拟机》)。
- 初始化:为前面的static变量赋初值、为成员变量赋初值;
- 三种类加载器
在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)
- Bootstrap:启动类加载器,加载JVM自身的类,主要是各种jar包,它从
<JAVA_HOME>/lib
路径下的核心类库或-Xbootclasspath
参数指定的路径下的jar包加载到内存中,而且Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类- Extension:是sun实现的sun.misc.Launcher$ExtClassLoader类,它负责加载
<JAVA_HOME>/lib/ext
目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,是一个静态内部类,用于开发者直接使用- System:是指 Sun公司实现的
sun.misc.Launcher$AppClassLoader,
它负责加载系统类路径java -classpath
或-D java.class.path
指定路径下的类库,是我们经常用到的classpath路径,一般情况下该类加载是程序中默认的类加载器,ClassLoader#getSystemClassLoader()
方法可以获取到该类加载器。
- 双亲委派机制
如果一个类收到了类加载的请求,他首先会提交给父类的加载器执行,如此一步步向上委托,如果父类能加载就加载,不能加载才会让子类去加载,他有以下作用:
- 避免类的重复加载,如果父类已经加载过这个类,子类就没必要再加载一遍;
- 防止核心API库被篡改,如果某个类通过双亲委派传递到启动类加载器,而核心API中发现有这个类,这样会直接返回已经被加载的api类,不会再去加载一遍。
- 引用计数算法
- 复制算法
- 标记清除算法
- 标记整理算法
- 分代回收算法
是指在新生代和老年代采取不同的回收算法,新生代用复制算法,老年代用标记整理或者标记清除算法;
- 一个对象到了堆里面,如果Eden区有容量就直接放在Eden区
- 如果Eden区不够放就会就进行一次minorGC,把Eden区的对象年龄加一,放到s0区
- 如果s0不够,就进行一次minorGC,把s0、Eden区的对象年龄加一,放到s1区
- s0和s1区的内容会一直如此反复,s0+eden=s1,s1+Eden=s0,所以一直有一块是空
- 因为每次复制年龄都会加一,到了15时对象就会自动传递到老年区
- 如果再来一个对象连老年区都不够放,就会先minorGC看看Eden区够不够,如果不够就fullGC;
- full GC 会将新生代和老年代都会进行回收,并且会使其他的进程全部停止,称为stop the word(STW),就是平常的卡顿,如果进行full GC 之后,内存还是不够的时候,就会抛出异常OOM。
- 如果一开始直接来了个大对象,新生代放不下会直接去老年代
枚举根节点做可达性分析比如上图,对象C就是对象不可达,是垃圾。
- 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Native方法)引用的对象
JVM有三种参数类型,标配参数、-X参数、-XX参数
- 标配参数
从jdk1.0到现在,都没变的命令:
java -version java -help java - showversion
- X参数
-Xint -Xcomp -Xmixed
- XX参数
又分为布尔类型和键值类型:
1、Boolean类型:
-XX:+或者-某个属性(+代表开启,-代表关闭)
终端使用jps -l命令查看当前进程,使用jinfo -flag查看是否开启-XX参数:
-XX:PrintGCDetails
使用-XX参数:
2、KV设值类型:
- 元空间大小和新生代对象年龄阈值设置:
配置metaspacesize:
- 参数显示:jinfo -flags 进程号
其中Non-default是默认的,Command line是人工新加的:
两个经典参数-Xms和-Xmx,这两个参数是-XX类型的。
可以通过以下三种方法罗列:
- -XX:+PrintFlagsinitia
- -XX:+PrintflagsFinal PrintFlagsFinal举例,运行java命令的同时打印出参数
- -XX:+PrintCommandLineFlags
等号前面有冒号代表被修改过的参数:
T是java运行类名,-XX:+PrintflagsFinal 可以运行时修改jvm参数:
这是一个内存16G的电脑设备,可见默认-xms是1/64倍物理内存大小,-xmx是1/4倍物理内存大小
- -Xms:等价于-XX:InitialHeapsize
- -Xmx:等价于-XX:MaxHeapsize
- -Xss:等价于-XX:ThreadStackSize,设置jvm栈大小;
下图演示了一个简单线程中,默认大小是0:
官网解释:
最后手动配置-Xss128k 结果:
- -Xmn:设置年轻代的大小
- -XX:MetaspaceSize:设置元空间大小(java8后元空间放在本地物理内存中,不在jvm中,所有受本地内存大小限制),默认才21M,所以一般生产中会设置大一点,要不然容易OOM
经典配置参考,给出了配置前和配置后jvm参数的变化,一般也就配这些参数:
- -XX:+PrintGCDetails:打印GC细节
案例:只分配10m的堆空间,但是建立一个对象为50m的数组:
可见控制台不仅报错,还给出了GC日志:
9728k是堆大小,大概10M,2560k是新生代大小,占三分之一,2048k=2560*0.8,满足1:1:8的新生代内部分区规则,最后fullGC都没能解决,直接报错OOM!!!
GC日志解析:
- -XX:SurvivorRatio
- -XX:NewRatio
默认情况:
- -XX:MaxTenuringThreshold
设置垃圾最大年龄,默认15
- 默认某对象实行GC复制算法最多15次,就不能呆在新生代了,要搬家去养老代。
- -XX:MaxTenuringThreshold=0:设置垃圾最大年龄为0的话,则年轻代对象不经过Surivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
- 而且只能在0-15内调:
Reference强、SoftReference软、WeakReference弱、PhantomReference虚
程序正常运行,0bj2是强引用,不会被回收,obj1置空所以被回收
内存够用,所以不会被回收
内存不够用,报错oom:
不管内存够不够,一律被回收:
一般软弱引用用于缓存:
就是实现了虚引用功能的hashmap,可见GC后hashmap被置空:
虚引用案例:把弱引用放在一个队列里:
执行gc后,引用都被回收,但是虚引用会输出引用队列的信息:
虚引用;类似于后置通知:
OOM和栈溢出都是错误,最常见的就是方法循环调用
对象占用堆空间过头:
GC一直运行:
案例:
全是fullGC,十四万次i:
可见GC每个区回收前后都是一样的,
一般NIO操作会出现这种OOM:
高并发场景,不能创建新的本地线程:
在单线程环境中,只有一个gc线程,执行gc时候会暂停用户线程,不适合服务器环境
有多个gc线程执行垃圾回收, 用户线程也会暂停,适用于科学计算和大数据处理首台处理等弱交互场景
用户线程和gc、可以同时执行,适用于强交互场景,但也会有内存碎片问题
java8诞生,将堆内存分为不同区域,并发进行垃圾回收
-XX:PrintCommandLineFlags -version
- UseSerialGc,一对一
- UseParallelGc,多对多
- UseiconcMarkSweepGc,(CMS)
- UseParNewGc,多对一
- UseParallelOldGc,
- UseG1Gc
SerialOld现在已经废弃,一般配完新生代,老年代也会自动配配置对应GC:
参数说明:
Server和Client模式:
- 新生代
1、Serial:养老区默认SerialOld
2、Parnew:养老区默认CMS
3、parallelGC:
- 老年代
4、parallelOld:年轻代自动也是并行收集
5、CMS: 年轻代自动parnew
CMS有四个过程:
- 初始标记(cMs initial mark)
- 并发标记(CMS concurrent mark)和用户线程一起
- 重新标记(CMs remark)
- 并发清除(CMS concurrent sweep)和用户线程一起
CMS优点是并发收集停顿低,缺点是cpu负担大。由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间
6、serialOld:
配置G1后,不分新生老年代,
从官网的描述中,我们知道G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。另外,它还具有以下特性:
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
G1的Stop The Word(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
1:G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW。
2:G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片。
3:宏观上看G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域(Region),可以近似理解为一个围棋的棋盘。
4:G1收集器里面讲整个的内存区都混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离的,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。
5:G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换:
区域化内存划片Region,整体编为了一些列不连续的内存区域,避免了全内存区的GC操作。
核心思想是将整个堆内存区域分成大小相同的子区域(Regjon),在JVM启动时会自动设置这些子区域的大小。
在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。
启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为:32MB*2048=65536MB=64G内存
过程演示:
回收步骤:
- 单CPU或小内存,单机程序-XX:+UseSerialGc
- 多CPU,需要最大吞吐量,如后台计算型应用
- -XX:+UseParallelGc
- -XX:+UseParalleloldGc
- 多CPU,追求低停顿时间,需快速响应如互联网应用
- -XX:+UseConcMarkSweepGc
- -XX:+ParewGc
load average系统负载均衡,分别代表一分钟、五分钟、十五分钟的平均负载值,如果三个数的平均值高于60%,一般就认为系统压力过大:
每两秒采样一次,共采样三次:
前两步很简单不解释,第三步:定位线程TID
第四步、转换十六进制:5102->13ee
五:打印前六十行日志:
找到是第十行代码出错:
一般工作中实现一个功能并不是从头开始,站在巨人肩膀上能看的更远,套用优秀框架能帮我们更好的工作和学习,所以要熟练使用GitHub,以下展示了一些常用github操作帮助自己更高效的学习优秀开源框架。
比如查一个秒杀功能:
eg:
组合查询:
优秀框架,一般用于学习:(redis正常查询会有四万多条记录,但awesome后只有四十多条)
网页地址后面#L加数字,可以高亮代码提示重要代码,方便程序员之间交流
一般常用t快捷键
创建引用的时候可以指定关联的队列,当GC释放对象内存的时候,会将引用加入到引用队列,如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动这相当于是一种通知机制。
学习视频来自尚硅谷,笔记为个人学习总结,新手上路多多包涵~~~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。