java内存
java动态运行时区域包括:方法区、虚拟机栈、本地方法栈、堆、程序计数器,如右图所示:
程序计数器
程序计数器用来标识要执行的代码的行号,为线程私有
虚拟机栈
为线程所私有
虚拟栈里存放的东西?跟线程有关?存放方法信息,包括方法名,方法参数,返回值地址等
存放局部变量表,操作数栈,动态链接,方法出口等信息
存在两种异常:
a.StackOverflowError(栈溢出异常),当方法深度大于栈深度时(如果线程请求的栈深度大于虚拟机所允许的深度)
b.OutOfMemory(内存溢出)没有更多的空间来存放线程空间
本地方法栈
和虚拟栈类似,区别是虚拟栈存储虚拟机方法,本地方法栈存储本地方法堆,几乎所有的对象都在堆中产生。
堆是GC的重点区域。根据对象特点可分为新生代和老年代。其中新生代又可以划分为eden,survivior1,survivor2
方法区
各个线程所共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
在HotSpot中,方法区又叫做永久代。存放一些不变的信息。类信息,常量,,,存在OOM异常
直接内存
OOM异常
在JDK1.4中新加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native中来回复制数据的开销。
常量池
String. intern()方法
如果常量池中存在该变量,则返回常量池中变量的指针引用。否则,把该变量添加进常量池,并返回该常量池的指针引用。
运行时常量是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息以外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
对象的创建
1.判断类是否加载,如果没有加载,需要加载类信息。加载完类信息以后,对象创建需要分配的内存空间是确定的。
2.为对象分配内存空间
如果可分配内存空间是连续的,则只需要在可用空间移动指针。(指针碰撞[Bump the Point])
如果可分配内存空间是不连续的,则需要维护一个剩余空间的指针列表。(空闲列表[Free List])
剩余空间是否连续与所采用的GC算法有关。例如:使用标记-清除算法(MS)剩余空间碎片不连续,使用复制算法或者标记-整理(mark-compact)算法剩余空间是连续的。
3.在堆中创建对象是很频繁的行为,所以需要确保在多线程中分配空间的安全性。有两种方式:
a.失败重试和CAS保证
b.虚拟机本地线程缓存(本地线程分配缓存[Thraed Local Allocation Buffer TLAB]),就是给每个线程分配一段内存空间,只有线程内存空间使用完或者不够新对象内存分配时才会涉及到线程之间的竞争。
4.对象初始化
分配完内存空间后,JVM会对新分配的对象进行初始化。基本数据类型被赋值为类型的初始值。
5.按照开发人员的编码初始化。
对象的访问定位
有两种方式
1.直接引用 直接指针
2.间接引用 使用句柄
优缺点
直接引用效率更快,间接引用在更改对象引用后不用修改指针引用。
使用直接指针最大的好处是速度快,因为它节省了一次指针定位的时间开销。
使用句柄访问的最大好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。
内存溢出
堆内存溢出 产生原因:堆中没有足够多的内存完成空间分配,并且无法扩展
限制参数:-Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError
实例代码:
public class HeapOoMException { public static void main(String[] args) { Listout = new ArrayList (); for (int i = 0; i < 16; i++) { out.add(_1MB._1Mb()); } } public static String _1Mb() { int kb = 1024; int mb = 512 * kb; char[] chars = new char[mb]; Arrays.fill(chars, 'f'); return new String(chars); }}
栈内存溢出
产生原因:线程请求栈深度大于虚拟机所允许的最大深度
限制参数:-Xss128k
实例代码:
public class StackOverFlowException { private static long stackLength = 0; public static void main(String[] args) throws Throwable { StackOverFlowException sof = new StackOverFlowException(); try { sof.stackLeak(); } catch (Throwable e) { System.out.println("stack length:" + stackLength); throw e; } } private void stackLeak() { stackLength++; stackLeak(); }}
方法区溢出
产生原因:由于动态代理等导致的类信息容量大于方法区容量
限制参数:-XX:PermSize=10m -XX:MaxPermSize=10m
实例代码:
public class VmMethodException { public static void main(String[] args) { while (true) { Enhancer enhance = new Enhancer(); enhance.setSuperclass(OomObject.class); enhance.setUseCache(false); MethodInterceptor mi = new MethodInterceptor() { public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invoke(o, objects); } }; enhance.setCallback(mi); enhance.create(); } } static class OomObject { }}