Java对象之家--Java堆
java堆一般被分为新生代和老年代。其中新生代存放新生对象,老年代存放老年对象(年龄判断见深入理解jvm二),新生代有可能分为eden区,s0区,s1区,s0和s1区也被称为from区和to区,他们是两块大小相等、可以互换角色的内存空间(实际上是为了进行垃圾回收,即复制算法)。
下面通过示例展示java堆、方法区、java栈之间的关系:
public class SimpleHeap { private int id; public SimpleHeap(int id) { super(); this.id = id; } public void show(){ System.out.println("My ID is "+id); } public static void main(String[] args) { SimpleHeap s1 = new SimpleHeap(1); SimpleHeap s2 = new SimpleHeap(2); s1.show(); s2.show(); } }
上述示例中,main函数创建的两个simpleHeap示例放在java堆中,描述simpleHeap类的信息存放在方法区,main函数中s1、s2局部变量存放在java栈中,并指向堆中的两个实例。
对象工位--出入java栈
Java虚拟机提供xss来指定线程的最大栈空间,也就决定了调用的最大深度。
实验一:探究栈空间的大小对函数调用深度的影响
public class TestStackDeep1 { private static int count = 0; private static void recursion(){ count++; recursion(); } public static void main(String[] args) { try{ recursion(); }catch(Throwable e){ System.out.println("deep of calling = "+count); e.printStackTrace(); } } }
1. jvm参数为
-Xss128K
输出结果为:
deep of calling = 1083 java.lang.StackOverflowError at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:6) at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:7) at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:7)
2.设置jvm参数为
-Xss256K
输出结果为:
deep of calling = 2726 java.lang.StackOverflowError at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:6) at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:7)
实验结果表明,java栈的空间越大,函数调用深度越大。
局部变量表
局部变量表用于保存函数的参数以及局部变量。局部变量表中的变量只在当前函数调用中有效,当前函数调用结束后,随着函数栈帧销毁。而函数的参数和局部变量的增多又会使局部变量表膨胀,占用的栈空间变大,从而减少可调用次数。
实验二:局部变量表的膨胀对函数调用深度的影响
public class TestStackDeep2 { private static int count =0 ; public static void recursion(long a,long b,long c){ long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10; count++; recursion(a, b, c); } public static void recursion(){ count++; recursion(); } public static void main(String[] args) { try{ recursion(0, 0, 0); //recursion(); }catch(Throwable e){ System.out.println("deep of calling = "+count); e.printStackTrace(); } } }
设置jvm参数为
-Xss128K当选用有参函数时,输出结果为:
deep of calling = 304 java.lang.StackOverflowError at chapter2_4.TestStackDeep2.recursion(TestStackDeep2.java:6) at chapter2_4.TestStackDeep2.recursion(TestStackDeep2.java:8)
用jclasslib可以看出,第一个recursion函数的最大局部变量表大小为:
它的局部变量表为
第二个recursion函数的最大局部变量表大小为:
它的局部变量表为
当选用无参函数时结果与实验一同参数结果一致,所以可以看到,但局部变量和函数参数增多时,局部变量表膨胀导致函数调用的最大深度下降。
实验三:探究局部变量表中的槽位是否可重用
public class Localvar { public void localvar1() { int a = 0; System.out.println(a); int b = 0; } public void localvar2() { { int a = 0; System.out.println(a); } int b=0; } }
查看localvavr1函数的局部变量表
查看函数localvar2的局部变量表
可以看到如果一个局部变量过了其作用域,那么在其作用域之后声明的局部变量就有可能过期局部变量的槽位,从而节省资源。
实验四:局部变量对垃圾回收的影响
public class LocalvarGC { public void localcarGC1(){//申请空间后立即回收 byte[] a = new byte[6*1024*1024]; System.gc(); } public void localcarGC2(){//先置为null,使其失去强引用再回收 byte[] a = new byte[6*1024*1024]; a=null; System.gc(); } public void localcarGC3(){//先使局部变量失效再回收 { byte[] a = new byte[6*1024*1024]; } System.gc(); } public void localcarGC4(){//使变量c复用变量a的字,对数组回收 { byte[] a = new byte[6*1024*1024]; } int c=10; System.gc(); } public void localcarGC5(){//调用函数1,再函数返回后在进行回收 localcarGC1(); System.gc(); } public static void main(String[] args) { LocalvarGC gc = new LocalvarGC(); gc.localcarGC1(); gc.localcarGC2(); gc.localcarGC3(); gc.localcarGC4(); gc.localcarGC5(); } }
设置jvm参数:
-XX:+PrintGC
输出结果为:
[GC 7475K->6744K(124416K), 0.0046744 secs] [Full GC 6744K->6606K(124416K), 0.0092658 secs]//无回收 [GC 14081K->6638K(124416K), 0.0003266 secs] [Full GC 6638K->461K(124416K), 0.0067632 secs]//有回收 [GC 7271K->6605K(124416K), 0.0011679 secs] [Full GC 6605K->6605K(124416K), 0.0030256 secs]//无回收 [GC 13415K->6605K(124416K), 0.0003572 secs] [Full GC 6605K->461K(124416K), 0.0060987 secs]//有回收 [GC 7271K->6605K(124416K), 0.0010584 secs] [Full GC 6605K->6605K(124416K), 0.0031301 secs]//无回收 [GC 6605K->6605K(124416K), 0.0002726 secs] [Full GC 6605K->461K(124416K), 0.0065306 secs]//有回收
可以看到,但局部变量的作用域失效、失去强引用、所在栈帧销毁时,才执行垃圾回收。
jvm优化--栈上分配
对于那些线程私有的对象,可以将他们打散分配在栈上,而不是分配在堆上,分配在栈上的好处是函数调用后自行销毁,不许进行垃圾回收,从而提高系统性能。栈上分配的基础是进行逃逸分析,判断对象作用域是否有可能逃逸出函数体
实验五:对非逃逸对象的栈上分配
public class OnStackTest { public static class User{ public int id; public String name; } //private static User u;//逃逸对象 public static void alloc(){ User u = new User();//非逃逸对象,该对象没有被alloc函数返回,未发生逃逸 u.id = 5; u.name = "geym"; } public static void main(String[] args) { long b = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { alloc(); } long e = System.currentTimeMillis(); System.out.println(e-b); } }
设置jvm参数(在server模式下启用逃逸分析并打印gc日志,同时开启标量替换):
-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
程序输出结果
7
可以看到,程序没有进行任何形式的gc回收就执行完成,说明user对象的分配过程被优化。
类的户口注册--方法区
大量类信息存放在方法区时会产生溢出
实验六 方法区的溢出观察
需要的jar包:
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class PermTest { static class OOMObject { } public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // TODO Auto-generated method stub return proxy.invokeSuper(obj, args); } }); enhancer.create(); } } }
输出结果:
[GC [PSYoungGen: 12016K->1247K(38400K)] 12016K->1255K(124416K), 0.0020854 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 1247K->0K(38400K)] [ParOldGen: 8K->1147K(64000K)] 1255K->1147K(102400K) [PSPermGen: 4095K->4094K(4096K)], 0.0148559 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [GC [PSYoungGen: 0K->0K(38400K)] 1147K->1147K(102400K), 0.0003406 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1147K->1147K(124928K)] 1147K->1147K(163328K) [PSPermGen: 4095K->4095K(4096K)], 0.0050305 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC [PSYoungGen: 0K->0K(38400K)] 1147K->1147K(163328K), 0.0002956 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1147K->968K(196096K)] 1147K->968K(234496K) [PSPermGen: 4095K->4095K(4096K)], 0.0115525 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [GC [PSYoungGen: 0K->0K(39424K)] 968K->968K(235520K), 0.0003338 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 0K->0K(39424K)] [ParOldGen: 968K->968K(309760K)] 968K->968K(349184K) [PSPermGen: 4095K->4090K(4096K)], 0.0116310 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] Exception in thread "main" [GC [PSYoungGen: 1392K->128K(35328K)] 2361K->1096K(345088K), 0.0005686 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 128K->0K(35328K)] [ParOldGen: 968K->789K(429568K)] 1096K->789K(464896K) [PSPermGen: 4094K->4094K(4096K)], 0.0122008 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] [GC [PSYoungGen: 0K->0K(36864K)] 789K->789K(466432K), 0.0003058 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 0K->0K(36864K)] [ParOldGen: 789K->789K(595456K)] 789K->789K(632320K) [PSPermGen: 4094K->4094K(4096K)], 0.0058110 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC [PSYoungGen: 1090K->96K(38400K)] 1880K->885K(633856K), 0.0004882 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 96K->0K(38400K)] [ParOldGen: 789K->798K(766464K)] 885K->798K(804864K) [PSPermGen: 4095K->4095K(4096K)], 0.0132138 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [GC [PSYoungGen: 737K->32K(39424K)] 1536K->830K(805888K), 0.0005683 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 32K->0K(39424K)] [ParOldGen: 798K->798K(986624K)] 830K->798K(1026048K) [PSPermGen: 4095K->4095K(4096K)], 0.0077948 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] [GC [PSYoungGen: 0K->0K(39424K)] 798K->798K(1026048K), 0.0002922 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 0K->0K(39424K)] [ParOldGen: 798K->798K(1223168K)] 798K->798K(1262592K) [PSPermGen: 4095K->4095K(4096K)], 0.0059662 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap PSYoungGen total 39424K, used 768K [0x00000000d5d00000, 0x00000000d8580000, 0x0000000100000000) eden space 38400K, 2% used [0x00000000d5d00000,0x00000000d5dc0030,0x00000000d8280000) from space 1024K, 0% used [0x00000000d8280000,0x00000000d8280000,0x00000000d8380000) to space 1024K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8580000) ParOldGen total 1223168K, used 798K [0x0000000081800000, 0x00000000cc280000, 0x00000000d5d00000) object space 1223168K, 0% used [0x0000000081800000,0x00000000818c7a80,0x00000000cc280000) PSPermGen total 4096K, used 4095K [0x0000000081400000, 0x0000000081800000, 0x0000000081800000) object space 4096K, 99% used [0x0000000081400000,0x00000000817ffff0,0x0000000081800000) Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
可以看到当大量的动态类生成后,方法区产生了溢出。要注意类和对象实例的区别,类主要是指类文件编译后的代码等数据,而实例是指从类文件中创建的对象。