- java内存分配
详细说明:
方法区:
1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载
2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
3. 该区域是被线程共享的。
4. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。
虚拟机栈:
1. 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
2. 虚拟机栈是线程私有的,它的生命周期与线程相同。
3. 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
4.操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式
5.每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。
本地方法栈
本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。
堆
java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。
程序计数器
内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。
-
jvm运行原理 开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件),再然后字节码被装入内存,一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行。
-
如和判断一个对象是否存活? 引用计数法
所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。
可达性算法
该算法的思想是:从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
在java中可以作为GC Roots的对象有以下几种:
虚拟机栈中引用的对象
方法区类静态属性引用的对象
方法区常量池引用的对象
本地方法栈JNI引用的对象
- 垃圾回收机制 四种引用类型:强引用、软引用、弱引用、虚引用
垃圾回收算法:标记-清除(Mark-Sweep)、复制(Copying)算法、标记-整理(Mark-Compact)、分代回收(generational collection)
垃圾回收的过程分为两步:
1.判断对象是否死亡
(1)引用计数器法:
①每当有一个对象引用是,计数器加一,当计数器为0是对象死亡
②缺点:无法解决循环引用的问题,假设A引用B,B引用A,那么这两个对象将不会被回收,造成内存泄漏
(2)可达性算法分析
①通过一系列可作为GC Roots的节点为起始点,从这些节点开始往下搜索,所走过的路径称为引用链。
②当一个对象到GC Roots节点没有引用链时,说明对象不可用
③可作为GC Roots节点的对象
虚拟机栈中引用的对象
本地方法栈中引用的对象
方法区中静态变量引用的对象
方法区中常量引用的对象
2.垃圾回收算法进行回收
(1)标记-清除:即直接将标记为死亡的对象清除,缺点是会产生垃圾碎片
(2)标记-整理:即将可用的对象同意向一端移动,将边界外的对象清除
(3)复制算法:即将堆分为了Eden,SurvivorFrom,SurvivorTo空间
①每次在Eden空间上分配对象
②SurvivorFrom空间为上次垃圾回收是还存活的对象
③SurvivorTo空间为本次垃圾回收是生存的对象存放的位置
④本次垃圾回收结束后交换SurvivorFrom与SurvivorTo
⑤复制算法需要担保空间,当有一个大的对象要分配,而Eden空间又不足时会直接分配到老年代
⑥在对象生存率较高时会进行大量的复制操作,降低效率
(4)分代回收算法:根据新生代与老年代对象的特点而使用不同的垃圾会回收算法
①新生代:对象生存周期较短,只有少量的生存对象,适合使用复制算法
②老年代:对象生存周期较长,只又少量需要回收的对象,且无担保空间,所以使用标记-整理算法或者是标记-清除算法
- 类初始化顺序
父类–静态变量/父类–静态初始化块
子类–静态变量/子类–静态初始化块
父类–变量/父类–初始化块
父类–构造器
子类–变量/子类–初始化块
子类–构造器
- 类加载器双亲委派模型机制
如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
双亲委派模型的好处:在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。
- 类加载器有哪些
- GC是什么时候触发的 由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Minor GC和Full GC。
Minor GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:
a) 年老代(Tenured)被写满;
b) 持久代(Perm)被写满;
c) System.gc()被显示调用;
d) 上一次GC之后Heap的各域分配策略动态变化;
- 内存溢出OOM
原因:
① 分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
② 应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
解决办法:
① java.lang.OutOfMemoryError: Java heap space ——>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
② java.lang.OutOfMemoryError: PermGen space ——>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
③ java.lang.StackOverflowError ——> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小
- volatile关键字 volatile的内存语义是
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存中。
当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量。
volatile底层的实现机制
如果把加入volatile关键字的代码和未加入volatile关键字的代码都生成汇编代码,会发现加入volatile关键字的代码会多出一个lock前缀指令。
1 、重排序时不能把后面的指令重排序到内存屏障之前的位置
2、使得本CPU的Cache写入内存
3、写入动作也会引起别的CPU或者别的内核无效化其Cache,相当于让新写入的值对别的线程可见。
volatile的使用场景
1、修饰状态变量
2、单例模式的实现,双重检查锁定
服务器cpu100%处理
-
top -c ,显示进程运行信息列表
-
键入P (大写p),进程按照CPU使用率排序
-
top -Hp 10765(上面命令查询到进程号) ,显示一个进程的线程运行信息列表
-
键入P (大写p),线程按照CPU使用率排序
-
printf “%x\n” 10804
-
结果: 0x7a0a
-
备注:之所以要转化为16进制,是因为堆栈里,线程id是用16进制表示的。
-
工具 pstack/jstack/grep
-
jstack 10765 | grep ‘0x2a34’ -C5 --color
-
打印进程堆栈,通过线程id,过滤得到线程堆栈
-
分析程序线程对应代码