來人啊給我炸了那個Java虛擬機
你指尖躍動的電光,是我此生不滅的信仰,唯我超電磁炮永世長存。
瞬間爆炸,完成單殺。
深度長文,非常非常長,執行這些程序可能導致機器完全死機,請遵照指示安全開車。
JVM中分了兩大塊,公共區域和棧私有區域。公共區域中有堆,用來放對象的。還有方法區,用來放一些類信息啊,方法信息啊或者一些運行時的常量信息的。棧私有區域中有分為,PC寄存器(下一條操作指令地址),棧(臨時的指針和數值)和本地方法區(native方法調用會用到)。
今天教大家怎么花式搞死Java虛擬機,順便大概知道一下GC是啥,先了解一下JVM內存的結構吧。
真實的GC信息是長這樣的。
- PSYoungGen total 3072K, used 128K
- eden space 2560K, 5% used
- survivor space
- from space 512K, 0% used
- to space 512K, 0% used
- ParOldGen total 6656K, used 408K
- object space 6656K, 6% used
- PSPermGen total 4096K, used 3039K
- object space 4096K, 74% used
一般的GC過程都是這樣的,***產生的對象,是可能***就要消滅嘛~對象先在Eden區出生,過一段時間GC掃描,如果對象還能用,那就丟到Survivor區。如果再過一段時間還能用,那就繼續丟到OldGen區。PerGem區呢,只會放一些Class類啊,方法啊,1.7之前字符串常量池也是放這里,只有Full GC的時候會進行回收。
有小伙伴就會問了,那為毛Survivor有兩個區,from和to?這是其中一個GC策略,每次GC在對Survivor區掃描的時候呢,會把有用的從from 直接 復制到to區,這兩個區是互相備份的,這樣就減少了內存碎片的信息收集了,這樣from-to-from-to來回來回好幾次,才把他們丟到老年代。
好了,開始花式吊打JVM了,先指定一下我們今天的JVM配置,大家自己配上,啊。
- -Xmx10m
- -XX:MaxPermSize=5m
- -XX:MaxDirectMemorySize=5m
- -XX:+PrintGCDetails
首先咱的主類長這樣。
- public class BlowUpJVM {
- }
既然說了是花式,今天的過程是這樣的。
- - [√] 棧深度溢出
- - [ ] ***代內存溢出
- - [ ] 本地方法棧溢出
- - [ ] JVM棧內存溢出
- - [ ] 堆溢出
- public static void testStackOverFlow(){
- BlowUpJVM.testStackOverFlow();
- }
棧不斷遞歸,而且沒有處理,所以虛擬機棧就不斷深入不斷深入,棧深度就這樣爆炸了。
- - [ ] 棧深度溢出
- - [√] ***代內存溢出
- - [ ] 本地方法棧溢出
- - [ ] JVM棧內存溢出
- - [ ] 堆溢出
- public static void testPergemOutOfMemory1(){
- //方法一失敗
- List<String> list = new ArrayList<String>();
- while(true){
- list.add(UUID.randomUUID().toString().intern());
- }
- }
打算把String常量池堆滿,沒想到失敗了,JDK1.7后常量池放到了堆里,也能進行垃圾回收了傲。
馬上第二次嘗試,使用cglib,用Class把老年代取堆滿,嗯,說走咱就走啊。
- public static void testPergemOutOfMemory2(){
- try {
- while (true) {
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(OOM.class);
- enhancer.setUseCache(false);
- enhancer.setCallback(new MethodInterceptor() {
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- return proxy.invokeSuper(obj, args);
- }
- });
- enhancer.create();
- }
- }
- catch (Exception e){
- e.printStackTrace();
- }
- }
虛擬機成功gg了,那JDK動態代理產生的類能不能撐爆呢?
- public static void testPergemOutOfMemory3(){
- while(true){
- final OOM oom = new OOM();
- Proxy.newProxyInstance(oom.getClass().getClassLoader(), oom.getClass().getInterfaces(), new InvocationHandler() {
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- Object result = method.invoke(oom, args);
- return result;
- }
- });
- }
- }
答案是不行!會進行回收。JDK動態代理產生的類信息,不會放到***代中,而是放在堆中。
- - [ ] 棧深度溢出
- - [ ] ***代內存溢出
- - [√] 本地方法棧溢出
- - [ ] JVM棧內存溢出
- - [ ] 堆溢出
- public static void testNativeMethodOutOfMemory(){
- int j = 0;
- while(true){
- Printer.println(j++);
- ExecutorService executors = Executors.newFixedThreadPool(50);
- int i=0;
- while(i++<10){
- executors.submit(new Runnable() {
- public void run() {
- }
- });
- }
- }
- }
這個的原理就是不斷創建線程池,而每個線程池都創建10個線程,這些線程池都是在本地方法區的,久而久之,本地方法區就爆炸了。
- - [ ] 棧深度溢出
- - [ ] ***代內存溢出
- - [ ] 本地方法棧溢出
- - [√] JVM棧內存溢出
- - [ ] 堆溢出
- public static void testStackOutOfMemory(){
- while (true) {
- Thread thread = new Thread(new Runnable() {
- public void run() {
- while(true){
- }
- }
- });
- thread.start();
- }
- }
線程的創建會直接在JVM棧中創建,但是本例子中,沒看到爆炸,主機先掛了,不是JVM掛了,真的是主機掛了,無論在mac還是在windows,都掛了。溫馨提示,這個真的會死機的。。
- - [ ] 棧深度溢出
- - [ ] ***代內存溢出
- - [ ] 本地方法棧溢出
- - [ ] JVM棧內存溢出
- - [√] 堆溢出
- public static void testOutOfHeapMemory(){
- List<StringBuffer> list = new ArrayList<StringBuffer>();
- while(true){
- StringBuffer B = new StringBuffer();
- for(int i = 0 ; i < 10000 ; i++){
- B.append(i);
- }
- list.add(B);
- }
- }
好了終于到了最簡單的環節,不斷往堆中塞新增的StringBuffer對象,堆滿了就直接爆炸了。
妥妥的。小伙伴們拿回去好好玩吧,就醬。
【本文為51CTO專欄作者“大蕉”的原創稿件,轉載請通過作者微信公眾號“一名叫大蕉的程序員”獲取授權】