38.代码优化-栈上分配、同步省略(锁消除)、分离对象或标量替换
1.代码优化
使用逃逸分析,编译器可以对代码做如下优化:
1.栈上分配:将堆分配转换为栈分配。
2.同步省略:一个对象只能被一个线程访问,那么对于这个对象的操作可以不考虑同步。
3.标量替换:有的对象有可能不需要作为一个连续的内存结构存在也可以被访问,那么对象的部分或者全部都可以放在CPU
寄存器中。由于Java是基于栈实现的,所以对于Java来说,对象的部分或者全部可以被放到栈中。
1.1 栈上分配
如果一个对象并没有逃逸出方法,就可能被优化成栈上分配。栈上分配的对象不需要进行垃圾回收,当线程结束,栈空间被回收的时候,局部变量对象也就一起被回收。
体验栈上分配的例子:
package jvn; // -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails public class StackAllocation { public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { alloc(); } // 查看执行时间 long end = System.currentTimeMillis(); System.out.println("花费的时间为: " + (end - start) + " ms"); // 为了方便查看堆内存中对象个数,线程sleep try { Thread.sleep(1000000); } catch (InterruptedException e1) { e1.printStackTrace(); } } private static void alloc() { User user = new User();//未发生逃逸 } static class User { } }
a)先设置JVM
参数:-Xmx256m -Xms256m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
,然后运行上面的代码。 -XX:-DoEscapeAnalysis
表示的是不启用逃逸分析。
得到的输出如下:可以发现是存在YGC
的,而且花费的时间较长。存在GC
是因为,创建的User
对象在堆区分配空间,当Eden
区满的时候,就会触发YGC
。
[GC (Allocation Failure) [PSYoungGen: 65536K->736K(76288K)] 65536K->744K(251392K), 0.0113754 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 66272K->784K(76288K)] 66280K->792K(251392K), 0.0009627 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 花费的时间为: 98 ms
b)先设置JVM
参数:-Xmx256m -Xms256m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
,然后运行上面的代码。 -XX:+DoEscapeAnalysis
表示的是启用逃逸分析。
得到的输出如下:是不存在GC
的。这是由于,User
对象没有发生逃逸,所以在栈上分配空间存储,栈是不存在GC
的。
花费的时间为: 6 ms
1.2 同步省略(锁消除)
如果同步代码块使用的锁对象只被一个线程访问而没有被其他线程使用。那么编译器会取消这部分代码的同步操作。
例子:
从两个角度来分析下面的代码:
a)这种代码的写法本来就是有问题的。加锁操作就不正确。锁的要求是所有的线程共用一个相同的锁,才有用。下面的代码,如果两个线程分别执行这段代码,它们会分别创建hollis
对象。两个线程使用的并不是同一个锁,所以同步代码块根本同步不了。
b)从同步省略的角度,由于锁对象hollis只会被一个线程自己使用,所以编译器会将同步代码块优化,消除同步。
1.3 分离对象或标量替换
1.什么是标量?标量是指一个无法再分解成更小的数据的类型。Java
中的原始的数据类型就是标量。
相对的,还可以进行分解的数据叫做聚合量。Java
中的对象就是聚合量,因为它还可以被分解成其他聚合量和标量。
如果经过逃逸分析,发现一个对象不会被外界访问的话,就会把这个对象拆解成若干个其中包含的成员变量来代替。 这个过程就是标量替换。
上面的point
对象,没有在方法外部被使用,就会被标量替换成:
int x = 1; int y = 2;
标量替换参数设置:默认标量替换是开启的。
体验标量替换:
package jvn; /** * 标量替换测试 * -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations */ public class ScalarReplace { public static class User { public int id; public String name; } public static void alloc() { User u = new User();//未发生逃逸 u.id = 5; u.name = "www.atguigu.com"; } public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { alloc(); } long end = System.currentTimeMillis(); System.out.println("花费的时间为: " + (end - start) + " ms"); } }
a)设置JVM
参数-Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
, -XX:-EliminateAllocations
表示的是不开启标量替换,然后运行代码,得到的输出如下:不开启标量替换的情况下,存在GC
,说明对象分配到了堆区。
[GC (Allocation Failure) 25600K->760K(98304K), 0.0010627 secs] [GC (Allocation Failure) 26360K->736K(98304K), 0.0008766 secs] [GC (Allocation Failure) 26336K->784K(98304K), 0.0007977 secs] [GC (Allocation Failure) 26384K->800K(98304K), 0.0008196 secs] [GC (Allocation Failure) 26400K->728K(98304K), 0.0007648 secs] [GC (Allocation Failure) 26328K->768K(101376K), 0.0008770 secs] [GC (Allocation Failure) 32512K->652K(101376K), 0.0008733 secs] [GC (Allocation Failure) 32396K->652K(100352K), 0.0004502 secs] 花费的时间为: 75 ms
b)设置JVM
参数-Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
, -XX:+EliminateAllocations
表示的是开启标量替换,然后运行代码,得到的输出如下:开启标量替换的情况下,不存在GC
,说明对象没有被分配到堆区,被标量替换成了成员变量,放到了栈区。
花费的时间为: 6 ms
参数介绍: